diff --git a/package.json b/package.json index ee90083..c2ceecd 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wayki-max", - "version": "2.2.2", + "version": "2.2.3", "description": "Wicc web wallet for chrome and firefox.", "author": "coredev@waykichainhk.com", "license": "MIT", @@ -31,7 +31,7 @@ "vue-slider-component": "^2.8.16", "vue2-toast": "^2.0.2", "vuex": "^3.1.1", - "wicc-wallet-lib": "^2.0.0" + "wicc-wallet-lib": "^2.1.0" }, "scripts": { "lint": "eslint --ext .js,.vue src", @@ -86,4 +86,4 @@ "webpack-merge": "^4.1.0", "ws": "3.3.2" } -} +} \ No newline at end of file diff --git a/src/backend/wallet/baas-api.js b/src/backend/wallet/baas-api.js index ec4d7b3..eaf9f01 100755 --- a/src/backend/wallet/baas-api.js +++ b/src/backend/wallet/baas-api.js @@ -19,12 +19,12 @@ const handleError = (error) => { export default class { constructor(network) { this.network = network || 'testnet' - if (localStorage.getItem('myselfNetWork')){ + if (localStorage.getItem('myselfNetWork')) { this.host = JSON.parse(localStorage.getItem('myselfNetWork')).url - }else{ + } else { if (this.network === 'mainnet') { this.host = BAAS_MAINNET - }else if (this.network === 'testnet') { + } else if (this.network === 'testnet') { this.host = BAAS_TESTNET } } @@ -39,7 +39,7 @@ export default class { } getAccountInfo(address) { - return axios.post(this.host + '/account/getaccountinfo',{'address':address}).then(handleResponse, handleError) + return axios.post(this.host + '/account/getaccountinfo', { 'address': address }).then(handleResponse, handleError) } getTxDetail(tx) { @@ -52,7 +52,7 @@ export default class { ///查询ERC20代币详情 getTokenInfo(regId, address) { // alert(this.host + '/contract/getcontractaccountinfo'+address+regId) - return axios.post(this.host + '/contract/getcontractaccountinfo',{ + return axios.post(this.host + '/contract/getcontractaccountinfo', { 'address': address, 'contractregid': regId.trim() }) @@ -71,23 +71,23 @@ export default class { ///获取wicc,wusd,wgrt交易记录 getTransHistory(info) { if (!info.address) throw new Error('address is required.') - return axios.post(this.host + '/transaction/gettranscationsbyaddressplus', { - "address": info.address, - "currentpage": info.currentpage, - "pagesize": info.pagesize, - "startheight": 1, - "trandirection": 0, - "txtype": "", - "coinsymbol":info.coinsymbol, + return axios.post(this.host + '/transaction/getwallettranscationsbyaddressplus', { + "address": info.address, + "currentpage": info.currentpage, + "pagesize": info.pagesize, + "startheight": 1, + "trandirection": 0, + "txtype": "", + "coinsymbol": info.coinsymbol, }).then(handleResponse, handleError) } ///获取 - getDetailInfo(info){ + getDetailInfo(info) { return axios.post(this.host + '/transaction/gettxdetailplus', info).then(handleResponse, handleError) } ///根据代号查询发型资产详情 - getAssetInfo(info){ + getAssetInfo(info) { return axios.post(this.host + '/asset/getasset', info).then(handleResponse, handleError) } //获取插件钱包最新版本号 diff --git a/src/backend/wallet/index.js b/src/backend/wallet/index.js index 0ede227..42e4ec6 100755 --- a/src/backend/wallet/index.js +++ b/src/backend/wallet/index.js @@ -29,7 +29,7 @@ const getSignInfo = (network, address) => { let srcRegId = "" const localRegid = localStorage.getItem('srcRegID') srcRegId = localRegid ? localRegid : "" - if (srcRegId!="") { + if (srcRegId != "") { return baasApi.getblockcount().then((data) => { const height = data const privateKey = vaultStorage.getPrivateKey(address) @@ -42,7 +42,7 @@ const getSignInfo = (network, address) => { } return baasApi.getAccountInfo(address).then((data) => { srcRegId = data.regid ? data.regid : "" - localStorage.setItem('srcRegID',srcRegId) + localStorage.setItem('srcRegID', srcRegId) return baasApi.getblockcount() }).then((data) => { const height = data @@ -341,7 +341,7 @@ export default { return { network, address: activeAddress, - regid:localRegid?localRegid:"", + regid: localRegid ? localRegid : "", } }, getTransHistory({ @@ -762,7 +762,9 @@ export default { coinType: info.coinType, assetType: info.assetType }; + console.log('will get dexPriceBuy hex'); let hex = wiccApi.dexPriceBuy(dexBuyLimitTxinfo) + console.log('hex', hex); return new BaasAPI(localNetWork).submitOfflineTrans(hex) }) }, @@ -862,7 +864,7 @@ export default { throw new Error('INVALID_VALUE') } let hex = wiccApi.createVariousCoinsTx(privateKey, height, srcRegId, info.destAddr, info.value, info.fees, info.coinType, info.feeSymbol, localNetWork, info.memo) - return {rawtx:hex} + return { rawtx: hex } }) }, @@ -914,13 +916,13 @@ export default { feesName: info.feesName, }; - - if (srcRegId){ + + if (srcRegId) { let hex = wiccApi.assetsPub(assestInfo) return new BaasAPI(localNetWork).submitOfflineTrans(hex) - }else{ + } else { return new Promise((resolve, reject) => { - reject("qianbaoweijihuo") + reject("qianbaoweijihuo") }) } }) @@ -952,12 +954,12 @@ export default { feesName: info.feesName, }; - if (srcRegId){ + if (srcRegId) { let hex = wiccApi.assetsUpdate(assestInfo) return new BaasAPI(localNetWork).submitOfflineTrans(hex) - }else{ + } else { return new Promise((resolve, reject) => { - reject("qianbaoweijihuo") + reject("qianbaoweijihuo") }) } }) @@ -991,14 +993,14 @@ export default { if (isNaN(parseFloat(info.amount))) { throw new Error('INVALID_VALUE') } - let hex = wiccApi.uContractInvoke(privateKey, height, srcRegId, info.regId, info.amount, info.coinSymbol,info.fees, info.feesName, info.contract, localNetWork, info.memo) + let hex = wiccApi.uContractInvoke(privateKey, height, srcRegId, info.regId, info.amount, info.coinSymbol, info.fees, info.feesName, info.contract, localNetWork, info.memo) return new BaasAPI(localNetWork).submitOfflineTrans(hex) }) }, - /** - * 多币种合约调用(仅仅签名) - */ + /** + * 多币种合约调用(仅仅签名) + */ variousCoinsContractRaw({ info }) { const localNetWork = localStorage.getItem('network') const wiccApi = getWiccApi(localNetWork) @@ -1010,8 +1012,8 @@ export default { if (isNaN(parseFloat(info.amount))) { throw new Error('INVALID_VALUE') } - let hex = wiccApi.uContractInvoke(privateKey, height, srcRegId, info.regId, info.amount, info.coinSymbol,info.fees, info.feesName, info.contract, localNetWork, info.memo) - return {rawtx:hex} + let hex = wiccApi.uContractInvoke(privateKey, height, srcRegId, info.regId, info.amount, info.coinSymbol, info.fees, info.feesName, info.contract, localNetWork, info.memo) + return { rawtx: hex } }) }, diff --git a/src/backend/wallet/lib/address.js b/src/backend/wallet/lib/address.js new file mode 100644 index 0000000..bf3b7d0 --- /dev/null +++ b/src/backend/wallet/lib/address.js @@ -0,0 +1,504 @@ +'use strict'; + +import _ from 'lodash'; +import $ from './util/preconditions'; +import errors from './errors'; +import Base58Check from './encoding/base58check'; +import Networks from './networks'; +import Hash from './crypto/hash'; +import JSUtil from './util/js'; +import PublicKey from './publickey'; + +/** + * Instantiate an address from an address String or Buffer, a public key or script hash Buffer, + * or an instance of {@link PublicKey} or {@link Script}. + * + * This is an immutable class, and if the first parameter provided to this constructor is an + * `Address` instance, the same argument will be returned. + * + * An address has two key properties: `network` and `type`. The type is either + * `Address.PayToPublicKeyHash` (value is the `'pubkeyhash'` string) + * or `Address.PayToScriptHash` (the string `'scripthash'`). The network is an instance of {@link Network}. + * You can quickly check whether an address is of a given kind by using the methods + * `isPayToPublicKeyHash` and `isPayToScriptHash` + * + * @example + * ```javascript + * // validate that an input field is valid + * var error = Address.getValidationError(input, 'testnet'); + * if (!error) { + * var address = Address(input, 'testnet'); + * } else { + * // invalid network or checksum (typo?) + * var message = error.messsage; + * } + * + * // get an address from a public key + * var address = Address(publicKey, 'testnet').toString(); + * ``` + * + * @param {*} data - The encoded data in various formats + * @param {Network|String|number=} network - The network: 'livenet' or 'testnet' + * @param {string=} type - The type of address: 'script' or 'pubkey' + * @returns {Address} A new valid and frozen instance of an Address + * @constructor + */ +function Address(data, network, type) { + /* jshint maxcomplexity: 12 */ + /* jshint maxstatements: 20 */ + + if (!(this instanceof Address)) { + return new Address(data, network, type); + } + + if (_.isArray(data) && _.isNumber(network)) { + return Address.createMultisig(data, network, type); + } + + if (data instanceof Address) { + // Immutable instance + return data; + } + + $.checkArgument(data, 'First argument is required, please include address data.', 'guide/address.html'); + + if (network && !Networks.get(network)) { + throw new TypeError('Second argument must be "livenet" or "testnet".'); + } + + if (type && (type !== Address.PayToPublicKeyHash && type !== Address.PayToScriptHash)) { + throw new TypeError('Third argument must be "pubkeyhash" or "scripthash".'); + } + + var info = this._classifyArguments(data, network, type); + + // set defaults if not set + info.network = info.network || Networks.get(network) || Networks.defaultNetwork; + info.type = info.type || type || Address.PayToPublicKeyHash; + + JSUtil.defineImmutable(this, { + hashBuffer: info.hashBuffer, + network: info.network, + type: info.type + }); + + return this; +} + +/** + * Internal function used to split different kinds of arguments of the constructor + * @param {*} data - The encoded data in various formats + * @param {Network|String|number=} network - The network: 'livenet' or 'testnet' + * @param {string=} type - The type of address: 'script' or 'pubkey' + * @returns {Object} An "info" object with "type", "network", and "hashBuffer" + */ +Address.prototype._classifyArguments = function (data, network, type) { + /* jshint maxcomplexity: 10 */ + // transform and validate input data + if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 20) { + return Address._transformHash(data); + } else if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 21) { + return Address._transformBuffer(data, network, type); + } else if (data instanceof PublicKey) { + return Address._transformPublicKey(data); + } else if (data instanceof Script) { + return Address._transformScript(data, network); + } else if (typeof (data) === 'string') { + return Address._transformString(data, network, type); + } else if (_.isObject(data)) { + return Address._transformObject(data); + } else { + throw new TypeError('First argument is an unrecognized data format.'); + } +}; + +/** @static */ +Address.PayToPublicKeyHash = 'pubkeyhash'; +/** @static */ +Address.PayToScriptHash = 'scripthash'; + +/** + * @param {Buffer} hash - An instance of a hash Buffer + * @returns {Object} An object with keys: hashBuffer + * @private + */ +Address._transformHash = function (hash) { + var info = {}; + if (!(hash instanceof Buffer) && !(hash instanceof Uint8Array)) { + throw new TypeError('Address supplied is not a buffer.'); + } + if (hash.length !== 20) { + throw new TypeError('Address hashbuffers must be exactly 20 bytes.'); + } + info.hashBuffer = hash; + return info; +}; + +/** + * Deserializes an address serialized through `Address#toObject()` + * @param {Object} data + * @param {string} data.hash - the hash that this address encodes + * @param {string} data.type - either 'pubkeyhash' or 'scripthash' + * @param {Network=} data.network - the name of the network associated + * @return {Address} + */ +Address._transformObject = function (data) { + $.checkArgument(data.hash || data.hashBuffer, 'Must provide a `hash` or `hashBuffer` property'); + $.checkArgument(data.type, 'Must provide a `type` property'); + return { + hashBuffer: data.hash ? Buffer.from(data.hash, 'hex') : data.hashBuffer, + network: Networks.get(data.network) || Networks.defaultNetwork, + type: data.type + }; +}; + +/** + * Internal function to discover the network and type based on the first data byte + * + * @param {Buffer} buffer - An instance of a hex encoded address Buffer + * @returns {Object} An object with keys: network and type + * @private + */ +Address._classifyFromVersion = function (buffer) { + var version = {}; + + var pubkeyhashNetwork = Networks.get(buffer[0], 'pubkeyhash'); + var scripthashNetwork = Networks.get(buffer[0], 'scripthash'); + + if (pubkeyhashNetwork) { + version.network = pubkeyhashNetwork; + version.type = Address.PayToPublicKeyHash; + } else if (scripthashNetwork) { + version.network = scripthashNetwork; + version.type = Address.PayToScriptHash; + } + + return version; +}; + +/** + * Internal function to transform a bitcoin address buffer + * + * @param {Buffer} buffer - An instance of a hex encoded address Buffer + * @param {string=} network - The network: 'livenet' or 'testnet' + * @param {string=} type - The type: 'pubkeyhash' or 'scripthash' + * @returns {Object} An object with keys: hashBuffer, network and type + * @private + */ +Address._transformBuffer = function (buffer, network, type) { + /* jshint maxcomplexity: 9 */ + var info = {}; + if (!(buffer instanceof Buffer) && !(buffer instanceof Uint8Array)) { + throw new TypeError('Address supplied is not a buffer.'); + } + if (buffer.length !== 1 + 20) { + throw new TypeError('Address buffers must be exactly 21 bytes.'); + } + + var networkObj = Networks.get(network); + var bufferVersion = Address._classifyFromVersion(buffer); + + if (network && !networkObj) { + throw new TypeError('Unknown network'); + } + + if (!bufferVersion.network || (networkObj && networkObj !== bufferVersion.network)) { + throw new TypeError('Address has mismatched network type.'); + } + + if (!bufferVersion.type || (type && type !== bufferVersion.type)) { + throw new TypeError('Address has mismatched type.'); + } + + info.hashBuffer = buffer.slice(1); + info.network = bufferVersion.network; + info.type = bufferVersion.type; + return info; +}; + +/** + * Internal function to transform a {@link PublicKey} + * + * @param {PublicKey} pubkey - An instance of PublicKey + * @returns {Object} An object with keys: hashBuffer, type + * @private + */ +Address._transformPublicKey = function (pubkey) { + var info = {}; + if (!(pubkey instanceof PublicKey)) { + throw new TypeError('Address must be an instance of PublicKey.'); + } + info.hashBuffer = Hash.sha256ripemd160(pubkey.toBuffer()); + info.type = Address.PayToPublicKeyHash; + return info; +}; + +/** + * Internal function to transform a {@link Script} into a `info` object. + * + * @param {Script} script - An instance of Script + * @returns {Object} An object with keys: hashBuffer, type + * @private + */ +Address._transformScript = function (script, network) { + $.checkArgument(script instanceof Script, 'script must be a Script instance'); + var info = script.getAddressInfo(network); + if (!info) { + throw new errors.Script.CantDeriveAddress(script); + } + return info; +}; + +/** + * Creates a P2SH address from a set of public keys and a threshold. + * + * The addresses will be sorted lexicographically, as that is the trend in bitcoin. + * To create an address from unsorted public keys, use the {@link Script#buildMultisigOut} + * interface. + * + * @param {Array} publicKeys - a set of public keys to create an address + * @param {number} threshold - the number of signatures needed to release the funds + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @param {boolean=} nestedWitness - if the address uses a nested p2sh witness + * @return {Address} + */ +Address.createMultisig = function (publicKeys, threshold, network, nestedWitness) { + network = network || publicKeys[0].network || Networks.defaultNetwork; + var redeemScript = Script.buildMultisigOut(publicKeys, threshold); + if (nestedWitness) { + return Address.payingTo(Script.buildWitnessMultisigOutFromScript(redeemScript), network); + } + return Address.payingTo(redeemScript, network); +}; + +/** + * Internal function to transform a bitcoin address string + * + * @param {string} data + * @param {String|Network=} network - either a Network instance, 'livenet', or 'testnet' + * @param {string=} type - The type: 'pubkeyhash' or 'scripthash' + * @returns {Object} An object with keys: hashBuffer, network and type + * @private + */ +Address._transformString = function (data, network, type) { + if (typeof (data) !== 'string') { + throw new TypeError('data parameter supplied is not a string.'); + } + data = data.trim(); + var addressBuffer = Base58Check.decode(data); + var info = Address._transformBuffer(addressBuffer, network, type); + return info; +}; + +/** + * Instantiate an address from a PublicKey instance + * + * @param {PublicKey} data + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.fromPublicKey = function (data, network) { + var info = Address._transformPublicKey(data); + network = network || Networks.defaultNetwork; + return new Address(info.hashBuffer, network, info.type); +}; + +/** + * Instantiate an address from a ripemd160 public key hash + * + * @param {Buffer} hash - An instance of buffer of the hash + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.fromPublicKeyHash = function (hash, network) { + var info = Address._transformHash(hash); + return new Address(info.hashBuffer, network, Address.PayToPublicKeyHash); +}; + +/** + * Instantiate an address from a ripemd160 script hash + * + * @param {Buffer} hash - An instance of buffer of the hash + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.fromScriptHash = function (hash, network) { + $.checkArgument(hash, 'hash parameter is required'); + var info = Address._transformHash(hash); + return new Address(info.hashBuffer, network, Address.PayToScriptHash); +}; + +/** + * Builds a p2sh address paying to script. This will hash the script and + * use that to create the address. + * If you want to extract an address associated with a script instead, + * see {{Address#fromScript}} + * + * @param {Script} script - An instance of Script + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.payingTo = function (script, network) { + $.checkArgument(script, 'script is required'); + $.checkArgument(script instanceof Script, 'script must be instance of Script'); + return Address.fromScriptHash(Hash.sha256ripemd160(script.toBuffer()), network); +}; + +/** + * Extract address from a Script. The script must be of one + * of the following types: p2pkh input, p2pkh output, p2sh input + * or p2sh output. + * This will analyze the script and extract address information from it. + * If you want to transform any script to a p2sh Address paying + * to that script's hash instead, use {{Address#payingTo}} + * + * @param {Script} script - An instance of Script + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.fromScript = function (script, network) { + $.checkArgument(script instanceof Script, 'script must be a Script instance'); + var info = Address._transformScript(script, network); + return new Address(info.hashBuffer, network, info.type); +}; + +/** + * Instantiate an address from a buffer of the address + * + * @param {Buffer} buffer - An instance of buffer of the address + * @param {String|Network=} network - either a Network instance, 'livenet', or 'testnet' + * @param {string=} type - The type of address: 'script' or 'pubkey' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.fromBuffer = function (buffer, network, type) { + var info = Address._transformBuffer(buffer, network, type); + return new Address(info.hashBuffer, info.network, info.type); +}; + +/** + * Instantiate an address from an address string + * + * @param {string} str - An string of the bitcoin address + * @param {String|Network=} network - either a Network instance, 'livenet', or 'testnet' + * @param {string=} type - The type of address: 'script' or 'pubkey' + * @returns {Address} A new valid and frozen instance of an Address + */ +Address.fromString = function (str, network, type) { + var info = Address._transformString(str, network, type); + return new Address(info.hashBuffer, info.network, info.type); +}; + +/** + * Instantiate an address from an Object + * + * @param {string} json - An JSON string or Object with keys: hash, network and type + * @returns {Address} A new valid instance of an Address + */ +Address.fromObject = function fromObject(obj) { + $.checkState( + JSUtil.isHexa(obj.hash), + 'Unexpected hash property, "' + obj.hash + '", expected to be hex.' + ); + var hashBuffer = Buffer.from(obj.hash, 'hex'); + return new Address(hashBuffer, obj.network, obj.type); +}; + +/** + * Will return a validation error if exists + * + * @example + * ```javascript + * // a network mismatch error + * var error = Address.getValidationError('15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2', 'testnet'); + * ``` + * + * @param {string} data - The encoded data + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @param {string} type - The type of address: 'script' or 'pubkey' + * @returns {null|Error} The corresponding error message + */ +Address.getValidationError = function (data, network, type) { + var error; + try { + /* jshint nonew: false */ + new Address(data, network, type); + } catch (e) { + error = e; + } + return error; +}; + +/** + * Will return a boolean if an address is valid + * + * @example + * ```javascript + * assert(Address.isValid('15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2', 'livenet')); + * ``` + * + * @param {string} data - The encoded data + * @param {String|Network} network - either a Network instance, 'livenet', or 'testnet' + * @param {string} type - The type of address: 'script' or 'pubkey' + * @returns {boolean} The corresponding error message + */ +Address.isValid = function (data, network, type) { + return !Address.getValidationError(data, network, type); +}; + +/** + * Returns true if an address is of pay to public key hash type + * @return boolean + */ +Address.prototype.isPayToPublicKeyHash = function () { + return this.type === Address.PayToPublicKeyHash; +}; + +/** + * Returns true if an address is of pay to script hash type + * @return boolean + */ +Address.prototype.isPayToScriptHash = function () { + return this.type === Address.PayToScriptHash; +}; + +/** + * Will return a buffer representation of the address + * + * @returns {Buffer} Bitcoin address buffer + */ +Address.prototype.toBuffer = function () { + var version = Buffer.from([this.network[this.type]]); + return Buffer.concat([version, this.hashBuffer]); +}; + +/** + * @returns {Object} A plain object with the address information + */ +Address.prototype.toObject = Address.prototype.toJSON = function toObject() { + return { + hash: this.hashBuffer.toString('hex'), + type: this.type, + network: this.network.toString() + }; +}; + +/** + * Will return a the string representation of the address + * + * @returns {string} Bitcoin address + */ +Address.prototype.toString = function () { + return Base58Check.encode(this.toBuffer()); +}; + +/** + * Will return a string formatted for the console + * + * @returns {string} Bitcoin address + */ +Address.prototype.inspect = function () { + return ''; +}; + +export default Address; +import Script from './script'; diff --git a/src/backend/wallet/lib/aes-cbc.js b/src/backend/wallet/lib/aes-cbc.js new file mode 100644 index 0000000..b5d9103 --- /dev/null +++ b/src/backend/wallet/lib/aes-cbc.js @@ -0,0 +1,34 @@ +import CryptoJS from 'crypto-js' + +var cbcEncrypt = function (key, iv, textBytes) { + + var encrypted = CryptoJS.AES.encrypt(textBytes, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }); + + var tmp = encrypted.toString() + + return tmp +} + +var cbcDecrypt = function (key, iv, encryptedBytes) { + var decrypted = ''; + decrypted = CryptoJS.AES.decrypt(encryptedBytes, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }); + + var tmp = decrypted.toString(CryptoJS.enc.Utf8) + + return tmp + +} + + +export default { + encrypt: cbcEncrypt, + decrypt: cbcDecrypt +} diff --git a/src/backend/wallet/lib/block/block.js b/src/backend/wallet/lib/block/block.js new file mode 100644 index 0000000..df6645b --- /dev/null +++ b/src/backend/wallet/lib/block/block.js @@ -0,0 +1,281 @@ +'use strict'; + +import _ from 'lodash'; +import BlockHeader from './blockheader'; +import BN from '../crypto/bn'; +import BufferUtil from '../util/buffer'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import Hash from '../crypto/hash'; +import Transaction from '../transaction'; +import $ from '../util/preconditions'; + +/** + * Instantiate a Block from a Buffer, JSON object, or Object with + * the properties of the Block + * + * @param {*} - A Buffer, JSON string, or Object + * @returns {Block} + * @constructor + */ +function Block(arg) { + if (!(this instanceof Block)) { + return new Block(arg); + } + _.extend(this, Block._from(arg)); + return this; +} + +// https://github.com/bitcoin/bitcoin/blob/b5fa132329f0377d787a4a21c1686609c2bfaece/src/primitives/block.h#L14 +Block.MAX_BLOCK_SIZE = 1000000; + +/** + * @param {*} - A Buffer, JSON string or Object + * @returns {Object} - An object representing block data + * @throws {TypeError} - If the argument was not recognized + * @private + */ +Block._from = function _from(arg) { + var info = {}; + if (BufferUtil.isBuffer(arg)) { + info = Block._fromBufferReader(BufferReader(arg)); + } else if (_.isObject(arg)) { + info = Block._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for Block'); + } + return info; +}; + +/** + * @param {Object} - A plain JavaScript object + * @returns {Object} - An object representing block data + * @private + */ +Block._fromObject = function _fromObject(data) { + var transactions = []; + data.transactions.forEach(function (tx) { + if (tx instanceof Transaction) { + transactions.push(tx); + } else { + transactions.push(Transaction().fromObject(tx)); + } + }); + var info = { + header: BlockHeader.fromObject(data.header), + transactions: transactions + }; + return info; +}; + +/** + * @param {Object} - A plain JavaScript object + * @returns {Block} - An instance of block + */ +Block.fromObject = function fromObject(obj) { + var info = Block._fromObject(obj); + return new Block(info); +}; + +/** + * @param {BufferReader} - Block data + * @returns {Object} - An object representing the block data + * @private + */ +Block._fromBufferReader = function _fromBufferReader(br) { + var info = {}; + $.checkState(!br.finished(), 'No block data received'); + info.header = BlockHeader.fromBufferReader(br); + var transactions = br.readVarintNum(); + info.transactions = []; + for (var i = 0; i < transactions; i++) { + info.transactions.push(Transaction().fromBufferReader(br)); + } + return info; +}; + +/** + * @param {BufferReader} - A buffer reader of the block + * @returns {Block} - An instance of block + */ +Block.fromBufferReader = function fromBufferReader(br) { + $.checkArgument(br, 'br is required'); + var info = Block._fromBufferReader(br); + return new Block(info); +}; + +/** + * @param {Buffer} - A buffer of the block + * @returns {Block} - An instance of block + */ +Block.fromBuffer = function fromBuffer(buf) { + return Block.fromBufferReader(new BufferReader(buf)); +}; + +/** + * @param {string} - str - A hex encoded string of the block + * @returns {Block} - A hex encoded string of the block + */ +Block.fromString = function fromString(str) { + var buf = Buffer.from(str, 'hex'); + return Block.fromBuffer(buf); +}; + +/** + * @param {Binary} - Raw block binary data or buffer + * @returns {Block} - An instance of block + */ +Block.fromRawBlock = function fromRawBlock(data) { + if (!BufferUtil.isBuffer(data)) { + data = Buffer.from(data, 'binary'); + } + var br = BufferReader(data); + br.pos = Block.Values.START_OF_BLOCK; + var info = Block._fromBufferReader(br); + return new Block(info); +}; + +/** + * @returns {Object} - A plain object with the block properties + */ +Block.prototype.toObject = Block.prototype.toJSON = function toObject() { + var transactions = []; + this.transactions.forEach(function (tx) { + transactions.push(tx.toObject()); + }); + return { + header: this.header.toObject(), + transactions: transactions + }; +}; + +/** + * @returns {Buffer} - A buffer of the block + */ +Block.prototype.toBuffer = function toBuffer() { + return this.toBufferWriter().concat(); +}; + +/** + * @returns {string} - A hex encoded string of the block + */ +Block.prototype.toString = function toString() { + return this.toBuffer().toString('hex'); +}; + +/** + * @param {BufferWriter} - An existing instance of BufferWriter + * @returns {BufferWriter} - An instance of BufferWriter representation of the Block + */ +Block.prototype.toBufferWriter = function toBufferWriter(bw) { + if (!bw) { + bw = new BufferWriter(); + } + bw.write(this.header.toBuffer()); + bw.writeVarintNum(this.transactions.length); + for (var i = 0; i < this.transactions.length; i++) { + this.transactions[i].toBufferWriter(bw); + } + return bw; +}; + +/** + * Will iterate through each transaction and return an array of hashes + * @returns {Array} - An array with transaction hashes + */ +Block.prototype.getTransactionHashes = function getTransactionHashes() { + var hashes = []; + if (this.transactions.length === 0) { + return [Block.Values.NULL_HASH]; + } + for (var t = 0; t < this.transactions.length; t++) { + hashes.push(this.transactions[t]._getHash()); + } + return hashes; +}; + +/** + * Will build a merkle tree of all the transactions, ultimately arriving at + * a single point, the merkle root. + * @link https://en.bitcoin.it/wiki/Protocol_specification#Merkle_Trees + * @returns {Array} - An array with each level of the tree after the other. + */ +Block.prototype.getMerkleTree = function getMerkleTree() { + + var tree = this.getTransactionHashes(); + + var j = 0; + for (var size = this.transactions.length; size > 1; size = Math.floor((size + 1) / 2)) { + for (var i = 0; i < size; i += 2) { + var i2 = Math.min(i + 1, size - 1); + var buf = Buffer.concat([tree[j + i], tree[j + i2]]); + tree.push(Hash.sha256sha256(buf)); + } + j += size; + } + + return tree; +}; + +/** + * Calculates the merkleRoot from the transactions. + * @returns {Buffer} - A buffer of the merkle root hash + */ +Block.prototype.getMerkleRoot = function getMerkleRoot() { + var tree = this.getMerkleTree(); + return tree[tree.length - 1]; +}; + +/** + * Verifies that the transactions in the block match the header merkle root + * @returns {Boolean} - If the merkle roots match + */ +Block.prototype.validMerkleRoot = function validMerkleRoot() { + + var h = new BN(this.header.merkleRoot.toString('hex'), 'hex'); + var c = new BN(this.getMerkleRoot().toString('hex'), 'hex'); + + if (h.cmp(c) !== 0) { + return false; + } + + return true; +}; + +/** + * @returns {Buffer} - The little endian hash buffer of the header + */ +Block.prototype._getHash = function () { + return this.header._getHash(); +}; + +var idProperty = { + configurable: false, + enumerable: true, + /** + * @returns {string} - The big endian hash buffer of the header + */ + get: function () { + if (!this._id) { + this._id = this.header.id; + } + return this._id; + }, + set: _.noop +}; +Object.defineProperty(Block.prototype, 'id', idProperty); +Object.defineProperty(Block.prototype, 'hash', idProperty); + +/** + * @returns {string} - A string formatted for the console + */ +Block.prototype.inspect = function inspect() { + return ''; +}; + +Block.Values = { + START_OF_BLOCK: 8, // Start of block in raw block data + NULL_HASH: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') +}; + +export default Block; diff --git a/src/backend/wallet/lib/block/blockheader.js b/src/backend/wallet/lib/block/blockheader.js new file mode 100644 index 0000000..ccfff9a --- /dev/null +++ b/src/backend/wallet/lib/block/blockheader.js @@ -0,0 +1,296 @@ +'use strict'; + +import _ from 'lodash'; +import BN from '../crypto/bn'; +import BufferUtil from '../util/buffer'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import Hash from '../crypto/hash'; +import JSUtil from '../util/js'; +import $ from '../util/preconditions'; + +var GENESIS_BITS = 0x1d00ffff; + +/** + * Instantiate a BlockHeader from a Buffer, JSON object, or Object with + * the properties of the BlockHeader + * + * @param {*} - A Buffer, JSON string, or Object + * @returns {BlockHeader} - An instance of block header + * @constructor + */ +var BlockHeader = function BlockHeader(arg) { + if (!(this instanceof BlockHeader)) { + return new BlockHeader(arg); + } + var info = BlockHeader._from(arg); + this.version = info.version; + this.prevHash = info.prevHash; + this.merkleRoot = info.merkleRoot; + this.time = info.time; + this.timestamp = info.time; + this.bits = info.bits; + this.nonce = info.nonce; + + if (info.hash) { + $.checkState( + this.hash === info.hash, + 'Argument object hash property does not match block hash.' + ); + } + + return this; +}; + +/** + * @param {*} - A Buffer, JSON string or Object + * @returns {Object} - An object representing block header data + * @throws {TypeError} - If the argument was not recognized + * @private + */ +BlockHeader._from = function _from(arg) { + var info = {}; + if (BufferUtil.isBuffer(arg)) { + info = BlockHeader._fromBufferReader(BufferReader(arg)); + } else if (_.isObject(arg)) { + info = BlockHeader._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for BlockHeader'); + } + return info; +}; + +/** + * @param {Object} - A JSON string + * @returns {Object} - An object representing block header data + * @private + */ +BlockHeader._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + var prevHash = data.prevHash; + var merkleRoot = data.merkleRoot; + if (_.isString(data.prevHash)) { + prevHash = BufferUtil.reverse(Buffer.from(data.prevHash, 'hex')); + } + if (_.isString(data.merkleRoot)) { + merkleRoot = BufferUtil.reverse(Buffer.from(data.merkleRoot, 'hex')); + } + var info = { + hash: data.hash, + version: data.version, + prevHash: prevHash, + merkleRoot: merkleRoot, + time: data.time, + timestamp: data.time, + bits: data.bits, + nonce: data.nonce + }; + return info; +}; + +/** + * @param {Object} - A plain JavaScript object + * @returns {BlockHeader} - An instance of block header + */ +BlockHeader.fromObject = function fromObject(obj) { + var info = BlockHeader._fromObject(obj); + return new BlockHeader(info); +}; + +/** + * @param {Binary} - Raw block binary data or buffer + * @returns {BlockHeader} - An instance of block header + */ +BlockHeader.fromRawBlock = function fromRawBlock(data) { + if (!BufferUtil.isBuffer(data)) { + data = Buffer.from(data, 'binary'); + } + var br = BufferReader(data); + br.pos = BlockHeader.Constants.START_OF_HEADER; + var info = BlockHeader._fromBufferReader(br); + return new BlockHeader(info); +}; + +/** + * @param {Buffer} - A buffer of the block header + * @returns {BlockHeader} - An instance of block header + */ +BlockHeader.fromBuffer = function fromBuffer(buf) { + var info = BlockHeader._fromBufferReader(BufferReader(buf)); + return new BlockHeader(info); +}; + +/** + * @param {string} - A hex encoded buffer of the block header + * @returns {BlockHeader} - An instance of block header + */ +BlockHeader.fromString = function fromString(str) { + var buf = Buffer.from(str, 'hex'); + return BlockHeader.fromBuffer(buf); +}; + +/** + * @param {BufferReader} - A BufferReader of the block header + * @returns {Object} - An object representing block header data + * @private + */ +BlockHeader._fromBufferReader = function _fromBufferReader(br) { + var info = {}; + info.version = br.readInt32LE(); + info.prevHash = br.read(32); + info.merkleRoot = br.read(32); + info.time = br.readUInt32LE(); + info.bits = br.readUInt32LE(); + info.nonce = br.readUInt32LE(); + return info; +}; + +/** + * @param {BufferReader} - A BufferReader of the block header + * @returns {BlockHeader} - An instance of block header + */ +BlockHeader.fromBufferReader = function fromBufferReader(br) { + var info = BlockHeader._fromBufferReader(br); + return new BlockHeader(info); +}; + +/** + * @returns {Object} - A plain object of the BlockHeader + */ +BlockHeader.prototype.toObject = BlockHeader.prototype.toJSON = function toObject() { + return { + hash: this.hash, + version: this.version, + prevHash: BufferUtil.reverse(this.prevHash).toString('hex'), + merkleRoot: BufferUtil.reverse(this.merkleRoot).toString('hex'), + time: this.time, + bits: this.bits, + nonce: this.nonce + }; +}; + +/** + * @returns {Buffer} - A Buffer of the BlockHeader + */ +BlockHeader.prototype.toBuffer = function toBuffer() { + return this.toBufferWriter().concat(); +}; + +/** + * @returns {string} - A hex encoded string of the BlockHeader + */ +BlockHeader.prototype.toString = function toString() { + return this.toBuffer().toString('hex'); +}; + +/** + * @param {BufferWriter} - An existing instance BufferWriter + * @returns {BufferWriter} - An instance of BufferWriter representation of the BlockHeader + */ +BlockHeader.prototype.toBufferWriter = function toBufferWriter(bw) { + if (!bw) { + bw = new BufferWriter(); + } + bw.writeInt32LE(this.version); + bw.write(this.prevHash); + bw.write(this.merkleRoot); + bw.writeUInt32LE(this.time); + bw.writeUInt32LE(this.bits); + bw.writeUInt32LE(this.nonce); + return bw; +}; + +/** + * Returns the target difficulty for this block + * @param {Number} bits + * @returns {BN} An instance of BN with the decoded difficulty bits + */ +BlockHeader.prototype.getTargetDifficulty = function getTargetDifficulty(bits) { + bits = bits || this.bits; + + var target = new BN(bits & 0xffffff); + var mov = 8 * ((bits >>> 24) - 3); + while (mov-- > 0) { + target = target.mul(new BN(2)); + } + return target; +}; + +/** + * @link https://en.bitcoin.it/wiki/Difficulty + * @return {Number} + */ +BlockHeader.prototype.getDifficulty = function getDifficulty() { + var difficulty1TargetBN = this.getTargetDifficulty(GENESIS_BITS).mul(new BN(Math.pow(10, 8))); + var currentTargetBN = this.getTargetDifficulty(); + + var difficultyString = difficulty1TargetBN.div(currentTargetBN).toString(10); + var decimalPos = difficultyString.length - 8; + difficultyString = difficultyString.slice(0, decimalPos) + '.' + difficultyString.slice(decimalPos); + + return parseFloat(difficultyString); +}; + +/** + * @returns {Buffer} - The little endian hash buffer of the header + */ +BlockHeader.prototype._getHash = function hash() { + var buf = this.toBuffer(); + return Hash.sha256sha256(buf); +}; + +var idProperty = { + configurable: false, + enumerable: true, + /** + * @returns {string} - The big endian hash buffer of the header + */ + get: function () { + if (!this._id) { + this._id = BufferReader(this._getHash()).readReverse().toString('hex'); + } + return this._id; + }, + set: _.noop +}; +Object.defineProperty(BlockHeader.prototype, 'id', idProperty); +Object.defineProperty(BlockHeader.prototype, 'hash', idProperty); + +/** + * @returns {Boolean} - If timestamp is not too far in the future + */ +BlockHeader.prototype.validTimestamp = function validTimestamp() { + var currentTime = Math.round(new Date().getTime() / 1000); + if (this.time > currentTime + BlockHeader.Constants.MAX_TIME_OFFSET) { + return false; + } + return true; +}; + +/** + * @returns {Boolean} - If the proof-of-work hash satisfies the target difficulty + */ +BlockHeader.prototype.validProofOfWork = function validProofOfWork() { + var pow = new BN(this.id, 'hex'); + var target = this.getTargetDifficulty(); + + if (pow.cmp(target) > 0) { + return false; + } + return true; +}; + +/** + * @returns {string} - A string formatted for the console + */ +BlockHeader.prototype.inspect = function inspect() { + return ''; +}; + +BlockHeader.Constants = { + START_OF_HEADER: 8, // Start buffer position in raw block data + MAX_TIME_OFFSET: 2 * 60 * 60, // The max a timestamp can be in the future + LARGEST_HASH: new BN('10000000000000000000000000000000000000000000000000000000000000000', 'hex') +}; + +export default BlockHeader; diff --git a/src/backend/wallet/lib/block/index.js b/src/backend/wallet/lib/block/index.js new file mode 100644 index 0000000..3d2285a --- /dev/null +++ b/src/backend/wallet/lib/block/index.js @@ -0,0 +1,6 @@ +export { default } from './block'; + +export { default as BlockHeader } from './blockheader' + +export { default as MerkleBlock } from './merkleblock' + diff --git a/src/backend/wallet/lib/block/merkleblock.js b/src/backend/wallet/lib/block/merkleblock.js new file mode 100644 index 0000000..5a2ef6f --- /dev/null +++ b/src/backend/wallet/lib/block/merkleblock.js @@ -0,0 +1,313 @@ +'use strict'; + +import _ from 'lodash'; +import BlockHeader from './blockheader'; +import BufferUtil from '../util/buffer'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import Hash from '../crypto/hash'; +import JSUtil from '../util/js'; +import Transaction from '../transaction'; +import errors from '../errors'; +import $ from '../util/preconditions'; + +/** + * Instantiate a MerkleBlock from a Buffer, JSON object, or Object with + * the properties of the Block + * + * @param {*} - A Buffer, JSON string, or Object representing a MerkleBlock + * @returns {MerkleBlock} + * @constructor + */ +function MerkleBlock(arg) { + /* jshint maxstatements: 18 */ + + if (!(this instanceof MerkleBlock)) { + return new MerkleBlock(arg); + } + + var info = {}; + if (BufferUtil.isBuffer(arg)) { + info = MerkleBlock._fromBufferReader(BufferReader(arg)); + } else if (_.isObject(arg)) { + var header; + if (arg.header instanceof BlockHeader) { + header = arg.header; + } else { + header = BlockHeader.fromObject(arg.header); + } + info = { + /** + * @name MerkleBlock#header + * @type {BlockHeader} + */ + header: header, + /** + * @name MerkleBlock#numTransactions + * @type {Number} + */ + numTransactions: arg.numTransactions, + /** + * @name MerkleBlock#hashes + * @type {String[]} + */ + hashes: arg.hashes, + /** + * @name MerkleBlock#flags + * @type {Number[]} + */ + flags: arg.flags + }; + } else { + throw new TypeError('Unrecognized argument for MerkleBlock'); + } + _.extend(this, info); + this._flagBitsUsed = 0; + this._hashesUsed = 0; + + return this; +} + +/** + * @param {Buffer} - MerkleBlock data in a Buffer object + * @returns {MerkleBlock} - A MerkleBlock object + */ +MerkleBlock.fromBuffer = function fromBuffer(buf) { + return MerkleBlock.fromBufferReader(BufferReader(buf)); +}; + +/** + * @param {BufferReader} - MerkleBlock data in a BufferReader object + * @returns {MerkleBlock} - A MerkleBlock object + */ +MerkleBlock.fromBufferReader = function fromBufferReader(br) { + return new MerkleBlock(MerkleBlock._fromBufferReader(br)); +}; + +/** + * @returns {Buffer} - A buffer of the block + */ +MerkleBlock.prototype.toBuffer = function toBuffer() { + return this.toBufferWriter().concat(); +}; + +/** + * @param {BufferWriter} - An existing instance of BufferWriter + * @returns {BufferWriter} - An instance of BufferWriter representation of the MerkleBlock + */ +MerkleBlock.prototype.toBufferWriter = function toBufferWriter(bw) { + if (!bw) { + bw = new BufferWriter(); + } + bw.write(this.header.toBuffer()); + bw.writeUInt32LE(this.numTransactions); + bw.writeVarintNum(this.hashes.length); + for (var i = 0; i < this.hashes.length; i++) { + bw.write(Buffer.from(this.hashes[i], 'hex')); + } + bw.writeVarintNum(this.flags.length); + for (i = 0; i < this.flags.length; i++) { + bw.writeUInt8(this.flags[i]); + } + return bw; +}; + +/** + * @returns {Object} - A plain object with the MerkleBlock properties + */ +MerkleBlock.prototype.toObject = MerkleBlock.prototype.toJSON = function toObject() { + return { + header: this.header.toObject(), + numTransactions: this.numTransactions, + hashes: this.hashes, + flags: this.flags + }; +}; + +/** + * Verify that the MerkleBlock is valid + * @returns {Boolean} - True/False whether this MerkleBlock is Valid + */ +MerkleBlock.prototype.validMerkleTree = function validMerkleTree() { + $.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array'); + $.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array'); + + // Can't have more hashes than numTransactions + if (this.hashes.length > this.numTransactions) { + return false; + } + + // Can't have more flag bits than num hashes + if (this.flags.length * 8 < this.hashes.length) { + return false; + } + + var height = this._calcTreeHeight(); + var opts = { hashesUsed: 0, flagBitsUsed: 0 }; + var root = this._traverseMerkleTree(height, 0, opts); + if (opts.hashesUsed !== this.hashes.length) { + return false; + } + return BufferUtil.equals(root, this.header.merkleRoot); +}; + +/** + * Return a list of all the txs hash that match the filter + * @returns {Array} - txs hash that match the filter + */ +MerkleBlock.prototype.filterdTxsHash = function filterdTxsHash() { + $.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array'); + $.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array'); + + // Can't have more hashes than numTransactions + if (this.hashes.length > this.numTransactions) { + throw new errors.MerkleBlock.InvalidMerkleTree(); + } + + // Can't have more flag bits than num hashes + if (this.flags.length * 8 < this.hashes.length) { + throw new errors.MerkleBlock.InvalidMerkleTree(); + } + + // If there is only one hash the filter do not match any txs in the block + if (this.hashes.length === 1) { + return []; + }; + + var height = this._calcTreeHeight(); + var opts = { hashesUsed: 0, flagBitsUsed: 0 }; + var txs = this._traverseMerkleTree(height, 0, opts, true); + if (opts.hashesUsed !== this.hashes.length) { + throw new errors.MerkleBlock.InvalidMerkleTree(); + } + return txs; +}; + +/** + * Traverse a the tree in this MerkleBlock, validating it along the way + * Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract() + * @param {Number} - depth - Current height + * @param {Number} - pos - Current position in the tree + * @param {Object} - opts - Object with values that need to be mutated throughout the traversal + * @param {Boolean} - checkForTxs - if true return opts.txs else return the Merkle Hash + * @param {Number} - opts.flagBitsUsed - Number of flag bits used, should start at 0 + * @param {Number} - opts.hashesUsed - Number of hashes used, should start at 0 + * @param {Array} - opts.txs - Will finish populated by transactions found during traversal that match the filter + * @returns {Buffer|null} - Buffer containing the Merkle Hash for that height + * @returns {Array} - transactions found during traversal that match the filter + * @private + */ +MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos, opts, checkForTxs) { + /* jshint maxcomplexity: 12*/ + /* jshint maxstatements: 20 */ + + opts = opts || {}; + opts.txs = opts.txs || []; + opts.flagBitsUsed = opts.flagBitsUsed || 0; + opts.hashesUsed = opts.hashesUsed || 0; + var checkForTxs = checkForTxs || false; + + if (opts.flagBitsUsed > this.flags.length * 8) { + return null; + } + var isParentOfMatch = (this.flags[opts.flagBitsUsed >> 3] >>> (opts.flagBitsUsed++ & 7)) & 1; + if (depth === 0 || !isParentOfMatch) { + if (opts.hashesUsed >= this.hashes.length) { + return null; + } + var hash = this.hashes[opts.hashesUsed++]; + if (depth === 0 && isParentOfMatch) { + opts.txs.push(hash); + } + return Buffer.from(hash, 'hex'); + } else { + var left = this._traverseMerkleTree(depth - 1, pos * 2, opts); + var right = left; + if (pos * 2 + 1 < this._calcTreeWidth(depth - 1)) { + right = this._traverseMerkleTree(depth - 1, pos * 2 + 1, opts); + } + if (checkForTxs) { + return opts.txs; + } else { + return Hash.sha256sha256(new Buffer.concat([left, right])); + }; + } +}; + +/** Calculates the width of a merkle tree at a given height. + * Modeled after Bitcoin Core merkleblock.h CalcTreeWidth() + * @param {Number} - Height at which we want the tree width + * @returns {Number} - Width of the tree at a given height + * @private + */ +MerkleBlock.prototype._calcTreeWidth = function calcTreeWidth(height) { + return (this.numTransactions + (1 << height) - 1) >> height; +}; + +/** Calculates the height of the merkle tree in this MerkleBlock + * @param {Number} - Height at which we want the tree width + * @returns {Number} - Height of the merkle tree in this MerkleBlock + * @private + */ +MerkleBlock.prototype._calcTreeHeight = function calcTreeHeight() { + var height = 0; + while (this._calcTreeWidth(height) > 1) { + height++; + } + return height; +}; + +/** + * @param {Transaction|String} - Transaction or Transaction ID Hash + * @returns {Boolean} - return true/false if this MerkleBlock has the TX or not + * @private + */ +MerkleBlock.prototype.hasTransaction = function hasTransaction(tx) { + $.checkArgument(!_.isUndefined(tx), 'tx cannot be undefined'); + $.checkArgument(tx instanceof Transaction || typeof tx === 'string', + 'Invalid tx given, tx must be a "string" or "Transaction"'); + + var hash = tx; + if (tx instanceof Transaction) { + // We need to reverse the id hash for the lookup + hash = BufferUtil.reverse(Buffer.from(tx.id, 'hex')).toString('hex'); + } + + var txs = []; + var height = this._calcTreeHeight(); + this._traverseMerkleTree(height, 0, { txs: txs }); + return txs.indexOf(hash) !== -1; +}; + +/** + * @param {Buffer} - MerkleBlock data + * @returns {Object} - An Object representing merkleblock data + * @private + */ +MerkleBlock._fromBufferReader = function _fromBufferReader(br) { + $.checkState(!br.finished(), 'No merkleblock data received'); + var info = {}; + info.header = BlockHeader.fromBufferReader(br); + info.numTransactions = br.readUInt32LE(); + var numHashes = br.readVarintNum(); + info.hashes = []; + for (var i = 0; i < numHashes; i++) { + info.hashes.push(br.read(32).toString('hex')); + } + var numFlags = br.readVarintNum(); + info.flags = []; + for (i = 0; i < numFlags; i++) { + info.flags.push(br.readUInt8()); + } + return info; +}; + +/** + * @param {Object} - A plain JavaScript object + * @returns {Block} - An instance of block + */ +MerkleBlock.fromObject = function fromObject(obj) { + return new MerkleBlock(obj); +}; + +export default MerkleBlock; diff --git a/src/backend/wallet/lib/crypto/bn.js b/src/backend/wallet/lib/crypto/bn.js new file mode 100644 index 0000000..532e8fe --- /dev/null +++ b/src/backend/wallet/lib/crypto/bn.js @@ -0,0 +1,202 @@ +'use strict'; + +import BN from 'bn.js'; +import $ from '../util/preconditions'; +import _ from 'lodash'; + +var reversebuf = function (buf) { + var buf2 = Buffer.alloc(buf.length); + for (var i = 0; i < buf.length; i++) { + buf2[i] = buf[buf.length - 1 - i]; + } + return buf2; +}; + +BN.Zero = new BN(0); +BN.One = new BN(1); +BN.Minus1 = new BN(-1); + +BN.fromNumber = function (n) { + $.checkArgument(_.isNumber(n)); + return new BN(n); +}; + +BN.fromString = function (str, base) { + $.checkArgument(_.isString(str)); + return new BN(str, base); +}; + +BN.fromBuffer = function (buf, opts) { + if (typeof opts !== 'undefined' && opts.endian === 'little') { + buf = reversebuf(buf); + } + var hex = buf.toString('hex'); + var bn = new BN(hex, 16); + return bn; +}; + +/** + * Instantiate a BigNumber from a "signed magnitude buffer" + * (a buffer where the most significant bit represents the sign (0 = positive, -1 = negative)) + */ +BN.fromSM = function (buf, opts) { + var ret; + if (buf.length === 0) { + return BN.fromBuffer(Buffer.from([0])); + } + + var endian = 'big'; + if (opts) { + endian = opts.endian; + } + if (endian === 'little') { + buf = reversebuf(buf); + } + + if (buf[0] & 0x80) { + buf[0] = buf[0] & 0x7f; + ret = BN.fromBuffer(buf); + ret.neg().copy(ret); + } else { + ret = BN.fromBuffer(buf); + } + return ret; +}; + + +BN.prototype.toNumber = function () { + return parseInt(this.toString(10), 10); +}; + +BN.prototype.toBuffer = function (opts) { + var buf, hex; + if (opts && opts.size) { + hex = this.toString(16, 2); + var natlen = hex.length / 2; + buf = Buffer.from(hex, 'hex'); + + if (natlen === opts.size) { + buf = buf; + } else if (natlen > opts.size) { + buf = BN.trim(buf, natlen); + } else if (natlen < opts.size) { + buf = BN.pad(buf, natlen, opts.size); + } + } else { + hex = this.toString(16, 2); + buf = Buffer.from(hex, 'hex'); + } + + if (typeof opts !== 'undefined' && opts.endian === 'little') { + buf = reversebuf(buf); + } + + return buf; +}; + +BN.prototype.toSMBigEndian = function () { + var buf; + if (this.cmp(BN.Zero) === -1) { + buf = this.neg().toBuffer(); + if (buf[0] & 0x80) { + buf = Buffer.concat([Buffer.from([0x80]), buf]); + } else { + buf[0] = buf[0] | 0x80; + } + } else { + buf = this.toBuffer(); + if (buf[0] & 0x80) { + buf = Buffer.concat([Buffer.from([0x00]), buf]); + } + } + + if (buf.length === 1 & buf[0] === 0) { + buf = Buffer.from([]); + } + return buf; +}; + +BN.prototype.toSM = function (opts) { + var endian = opts ? opts.endian : 'big'; + var buf = this.toSMBigEndian(); + + if (endian === 'little') { + buf = reversebuf(buf); + } + return buf; +}; + +/** + * Create a BN from a "ScriptNum": + * This is analogous to the constructor for CScriptNum in bitcoind. Many ops in + * bitcoind's script interpreter use CScriptNum, which is not really a proper + * bignum. Instead, an error is thrown if trying to input a number bigger than + * 4 bytes. We copy that behavior here. A third argument, `size`, is provided to + * extend the hard limit of 4 bytes, as some usages require more than 4 bytes. + */ +BN.fromScriptNumBuffer = function (buf, fRequireMinimal, size) { + var nMaxNumSize = size || 4; + $.checkArgument(buf.length <= nMaxNumSize, new Error('script number overflow')); + if (fRequireMinimal && buf.length > 0) { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if ((buf[buf.length - 1] & 0x7f) === 0) { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) { + throw new Error('non-minimally encoded script number'); + } + } + } + return BN.fromSM(buf, { + endian: 'little' + }); +}; + +/** + * The corollary to the above, with the notable exception that we do not throw + * an error if the output is larger than four bytes. (Which can happen if + * performing a numerical operation that results in an overflow to more than 4 + * bytes). + */ +BN.prototype.toScriptNumBuffer = function () { + return this.toSM({ + endian: 'little' + }); +}; + +BN.prototype.gt = function (b) { + return this.cmp(b) > 0; +}; + +BN.prototype.gte = function (b) { + return this.cmp(b) >= 0; +}; + +BN.prototype.lt = function (b) { + return this.cmp(b) < 0; +}; + +BN.trim = function (buf, natlen) { + return buf.slice(natlen - buf.length, buf.length); +}; + +BN.pad = function (buf, natlen, size) { + var rbuf = Buffer.alloc(size); + for (var i = 0; i < buf.length; i++) { + rbuf[rbuf.length - 1 - i] = buf[buf.length - 1 - i]; + } + for (i = 0; i < size - natlen; i++) { + rbuf[i] = 0; + } + return rbuf; +}; + +export default BN; diff --git a/src/backend/wallet/lib/crypto/ecdsa.js b/src/backend/wallet/lib/crypto/ecdsa.js new file mode 100644 index 0000000..d524229 --- /dev/null +++ b/src/backend/wallet/lib/crypto/ecdsa.js @@ -0,0 +1,296 @@ +'use strict'; + +import BN from './bn'; +import Point from './point'; +import Signature from './signature'; +import PublicKey from '../publickey'; +import Random from './random'; +import Hash from './hash'; +import BufferUtil from '../util/buffer'; +import _ from 'lodash'; +import $ from '../util/preconditions'; + +var ECDSA = function ECDSA(obj) { + if (!(this instanceof ECDSA)) { + return new ECDSA(obj); + } + if (obj) { + this.set(obj); + } +}; + +/* jshint maxcomplexity: 9 */ +ECDSA.prototype.set = function (obj) { + this.hashbuf = obj.hashbuf || this.hashbuf; + this.endian = obj.endian || this.endian; //the endianness of hashbuf + this.privkey = obj.privkey || this.privkey; + this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey); + this.sig = obj.sig || this.sig; + this.k = obj.k || this.k; + this.verified = obj.verified || this.verified; + return this; +}; + +ECDSA.prototype.privkey2pubkey = function () { + this.pubkey = this.privkey.toPublicKey(); +}; + +ECDSA.prototype.calci = function () { + for (var i = 0; i < 4; i++) { + this.sig.i = i; + var Qprime; + try { + Qprime = this.toPublicKey(); + } catch (e) { + console.error(e); + continue; + } + + if (Qprime.point.eq(this.pubkey.point)) { + this.sig.compressed = this.pubkey.compressed; + return this; + } + } + + this.sig.i = undefined; + throw new Error('Unable to find valid recovery factor'); +}; + +ECDSA.fromString = function (str) { + var obj = JSON.parse(str); + return new ECDSA(obj); +}; + +ECDSA.prototype.randomK = function () { + var N = Point.getN(); + var k; + do { + k = BN.fromBuffer(Random.getRandomBuffer(32)); + } while (!(k.lt(N) && k.gt(BN.Zero))); + this.k = k; + return this; +}; + + +// https://tools.ietf.org/html/rfc6979#section-3.2 +ECDSA.prototype.deterministicK = function (badrs) { + /* jshint maxstatements: 25 */ + // if r or s were invalid when this function was used in signing, + // we do not want to actually compute r, s here for efficiency, so, + // we can increment badrs. explained at end of RFC 6979 section 3.2 + if (_.isUndefined(badrs)) { + badrs = 0; + } + var v = Buffer.alloc(32); + v.fill(0x01); + var k = Buffer.alloc(32); + k.fill(0x00); + var x = this.privkey.bn.toBuffer({ + size: 32 + }); + var hashbuf = this.endian === 'little' ? BufferUtil.reverse(this.hashbuf) : this.hashbuf + k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00]), x, hashbuf]), k); + v = Hash.sha256hmac(v, k); + k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x01]), x, hashbuf]), k); + v = Hash.sha256hmac(v, k); + v = Hash.sha256hmac(v, k); + var T = BN.fromBuffer(v); + var N = Point.getN(); + + // also explained in 3.2, we must ensure T is in the proper range (0, N) + for (var i = 0; i < badrs || !(T.lt(N) && T.gt(BN.Zero)); i++) { + k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00])]), k); + v = Hash.sha256hmac(v, k); + v = Hash.sha256hmac(v, k); + T = BN.fromBuffer(v); + } + + this.k = T; + return this; +}; + +// Information about public key recovery: +// https://bitcointalk.org/index.php?topic=6430.0 +// http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k +ECDSA.prototype.toPublicKey = function () { + /* jshint maxstatements: 25 */ + var i = this.sig.i; + $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be equal to 0, 1, 2, or 3')); + + var e = BN.fromBuffer(this.hashbuf); + var r = this.sig.r; + var s = this.sig.s; + + // A set LSB signifies that the y-coordinate is odd + var isYOdd = i & 1; + + // The more significant bit specifies whether we should use the + // first or second candidate key. + var isSecondKey = i >> 1; + + var n = Point.getN(); + var G = Point.getG(); + + // 1.1 Let x = r + jn + var x = isSecondKey ? r.add(n) : r; + var R = Point.fromX(isYOdd, x); + + // 1.4 Check that nR is at infinity + var nR = R.mul(n); + + if (!nR.isInfinity()) { + throw new Error('nR is not a valid curve point'); + } + + // Compute -e from e + var eNeg = e.neg().umod(n); + + // 1.6.1 Compute Q = r^-1 (sR - eG) + // Q = r^-1 (sR + -eG) + var rInv = r.invm(n); + + //var Q = R.multiplyTwo(s, G, eNeg).mul(rInv); + var Q = R.mul(s).add(G.mul(eNeg)).mul(rInv); + + var pubkey = PublicKey.fromPoint(Q, this.sig.compressed); + + return pubkey; +}; + +ECDSA.prototype.sigError = function () { + /* jshint maxstatements: 25 */ + if (!BufferUtil.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { + return 'hashbuf must be a 32 byte buffer'; + } + + var r = this.sig.r; + var s = this.sig.s; + if (!(r.gt(BN.Zero) && r.lt(Point.getN())) || !(s.gt(BN.Zero) && s.lt(Point.getN()))) { + return 'r and s not in range'; + } + + var e = BN.fromBuffer(this.hashbuf, this.endian ? { + endian: this.endian + } : undefined); + var n = Point.getN(); + var sinv = s.invm(n); + var u1 = sinv.mul(e).umod(n); + var u2 = sinv.mul(r).umod(n); + + var p = Point.getG().mulAdd(u1, this.pubkey.point, u2); + if (p.isInfinity()) { + return 'p is infinity'; + } + + if (p.getX().umod(n).cmp(r) !== 0) { + return 'Invalid signature'; + } else { + return false; + } +}; + +ECDSA.toLowS = function (s) { + //enforce low s + //see BIP 62, "low S values in signatures" + if (s.gt(BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')))) { + s = Point.getN().sub(s); + } + return s; +}; + +ECDSA.prototype._findSignature = function (d, e) { + var N = Point.getN(); + var G = Point.getG(); + // try different values of k until r, s are valid + var badrs = 0; + var k, Q, r, s; + do { + if (!this.k || badrs > 0) { + this.deterministicK(badrs); + } + badrs++; + k = this.k; + Q = G.mul(k); + r = Q.x.umod(N); + s = k.invm(N).mul(e.add(d.mul(r))).umod(N); + } while (r.cmp(BN.Zero) <= 0 || s.cmp(BN.Zero) <= 0); + + s = ECDSA.toLowS(s); + return { + s: s, + r: r + }; + +}; + +ECDSA.prototype.sign = function () { + var hashbuf = this.hashbuf; + var privkey = this.privkey; + var d = privkey.bn; + + $.checkState(hashbuf && privkey && d, new Error('invalid parameters')); + $.checkState(BufferUtil.isBuffer(hashbuf) && hashbuf.length === 32, new Error('hashbuf must be a 32 byte buffer')); + + var e = BN.fromBuffer(hashbuf, this.endian ? { + endian: this.endian + } : undefined); + + var obj = this._findSignature(d, e); + obj.compressed = this.pubkey.compressed; + + this.sig = new Signature(obj); + return this; +}; + +ECDSA.prototype.signRandomK = function () { + this.randomK(); + return this.sign(); +}; + +ECDSA.prototype.toString = function () { + var obj = {}; + if (this.hashbuf) { + obj.hashbuf = this.hashbuf.toString('hex'); + } + if (this.privkey) { + obj.privkey = this.privkey.toString(); + } + if (this.pubkey) { + obj.pubkey = this.pubkey.toString(); + } + if (this.sig) { + obj.sig = this.sig.toString(); + } + if (this.k) { + obj.k = this.k.toString(); + } + return JSON.stringify(obj); +}; + +ECDSA.prototype.verify = function () { + if (!this.sigError()) { + this.verified = true; + } else { + this.verified = false; + } + return this; +}; + +ECDSA.sign = function (hashbuf, privkey, endian) { + return ECDSA().set({ + hashbuf: hashbuf, + endian: endian, + privkey: privkey + }).sign().sig; +}; + +ECDSA.verify = function (hashbuf, sig, pubkey, endian) { + return ECDSA().set({ + hashbuf: hashbuf, + endian: endian, + sig: sig, + pubkey: pubkey + }).verify().verified; +}; + +export default ECDSA; diff --git a/src/backend/wallet/lib/crypto/hash.js b/src/backend/wallet/lib/crypto/hash.js new file mode 100644 index 0000000..fe4e144 --- /dev/null +++ b/src/backend/wallet/lib/crypto/hash.js @@ -0,0 +1,89 @@ +'use strict'; + +import crypto from 'crypto'; +import BufferUtil from '../util/buffer'; +import $ from '../util/preconditions'; + +const Hash = {}; + +Hash.sha1 = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return crypto.createHash('sha1').update(buf).digest(); +}; + +Hash.sha1.blocksize = 512; + +Hash.sha256 = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return crypto.createHash('sha256').update(buf).digest(); +}; + +Hash.sha256.blocksize = 512; + +Hash.sha256sha256 = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return Hash.sha256(Hash.sha256(buf)); +}; + +Hash.ripemd160 = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return crypto.createHash('ripemd160').update(buf).digest(); +}; + +Hash.sha256ripemd160 = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return Hash.ripemd160(Hash.sha256(buf)); +}; + +Hash.sha512 = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return crypto.createHash('sha512').update(buf).digest(); +}; + +Hash.sha512.blocksize = 1024; + +Hash.hmac = function (hashf, data, key) { + //http://en.wikipedia.org/wiki/Hash-based_message_authentication_code + //http://tools.ietf.org/html/rfc4868#section-2 + $.checkArgument(BufferUtil.isBuffer(data)); + $.checkArgument(BufferUtil.isBuffer(key)); + $.checkArgument(hashf.blocksize); + + var blocksize = hashf.blocksize / 8; + + if (key.length > blocksize) { + key = hashf(key); + } else if (key < blocksize) { + var fill = Buffer.alloc(blocksize); + fill.fill(0); + key.copy(fill); + key = fill; + } + + var o_key = Buffer.alloc(blocksize); + o_key.fill(0x5c); + + var i_key = Buffer.alloc(blocksize); + i_key.fill(0x36); + + var o_key_pad = Buffer.alloc(blocksize); + var i_key_pad = Buffer.alloc(blocksize); + for (var i = 0; i < blocksize; i++) { + o_key_pad[i] = o_key[i] ^ key[i]; + i_key_pad[i] = i_key[i] ^ key[i]; + } + + return hashf(Buffer.concat([o_key_pad, hashf(Buffer.concat([i_key_pad, data]))])); +}; + +Hash.sha256hmac = function (data, key) { + return Hash.hmac(Hash.sha256, data, key); +}; + +Hash.sha512hmac = function (data, key) { + return Hash.hmac(Hash.sha512, data, key); +}; + +export default Hash + +export const sha256sha256 = Hash.sha256sha256 \ No newline at end of file diff --git a/src/backend/wallet/lib/crypto/point.js b/src/backend/wallet/lib/crypto/point.js new file mode 100644 index 0000000..865f21c --- /dev/null +++ b/src/backend/wallet/lib/crypto/point.js @@ -0,0 +1,150 @@ +'use strict'; + +import BN from './bn'; +import BufferUtil from '../util/buffer'; + +import { ec as EC } from 'elliptic'; +var ec = new EC('secp256k1'); +var ecPoint = ec.curve.point.bind(ec.curve); +var ecPointFromX = ec.curve.pointFromX.bind(ec.curve); + +/** + * + * Instantiate a valid secp256k1 Point from the X and Y coordinates. + * + * @param {BN|String} x - The X coordinate + * @param {BN|String} y - The Y coordinate + * @link https://github.com/indutny/elliptic + * @augments elliptic.curve.point + * @throws {Error} A validation error if exists + * @returns {Point} An instance of Point + * @constructor + */ +var Point = function Point(x, y, isRed) { + try { + var point = ecPoint(x, y, isRed); + } catch (e) { + throw new Error('Invalid Point'); + } + point.validate(); + return point; +}; + +Point.prototype = Object.getPrototypeOf(ec.curve.point()); + +/** + * + * Instantiate a valid secp256k1 Point from only the X coordinate + * + * @param {boolean} odd - If the Y coordinate is odd + * @param {BN|String} x - The X coordinate + * @throws {Error} A validation error if exists + * @returns {Point} An instance of Point + */ +Point.fromX = function fromX(odd, x) { + try { + var point = ecPointFromX(x, odd); + } catch (e) { + throw new Error('Invalid X'); + } + point.validate(); + return point; +}; + +/** + * + * Will return a secp256k1 ECDSA base point. + * + * @link https://en.bitcoin.it/wiki/Secp256k1 + * @returns {Point} An instance of the base point. + */ +Point.getG = function getG() { + return ec.curve.g; +}; + +/** + * + * Will return the max of range of valid private keys as governed by the secp256k1 ECDSA standard. + * + * @link https://en.bitcoin.it/wiki/Private_key#Range_of_valid_ECDSA_private_keys + * @returns {BN} A BN instance of the number of points on the curve + */ +Point.getN = function getN() { + return new BN(ec.curve.n.toArray()); +}; + +Point.prototype._getX = Point.prototype.getX; + +/** + * + * Will return the X coordinate of the Point + * + * @returns {BN} A BN instance of the X coordinate + */ +Point.prototype.getX = function getX() { + return new BN(this._getX().toArray()); +}; + +Point.prototype._getY = Point.prototype.getY; + +/** + * + * Will return the Y coordinate of the Point + * + * @returns {BN} A BN instance of the Y coordinate + */ +Point.prototype.getY = function getY() { + return new BN(this._getY().toArray()); +}; + +/** + * + * Will determine if the point is valid + * + * @link https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf + * @param {Point} An instance of Point + * @throws {Error} A validation error if exists + * @returns {Point} An instance of the same Point + */ +Point.prototype.validate = function validate() { + + if (this.isInfinity()) { + throw new Error('Point cannot be equal to Infinity'); + } + + var p2; + try { + p2 = ecPointFromX(this.getX(), this.getY().isOdd()); + } catch (e) { + throw new Error('Point does not lie on the curve'); + } + + if (p2.y.cmp(this.y) !== 0) { + throw new Error('Invalid y value for curve.'); + } + + + //todo: needs test case + if (!(this.mul(Point.getN()).isInfinity())) { + throw new Error('Point times N must be infinity'); + } + + return this; + +}; + +Point.pointToCompressed = function pointToCompressed(point) { + var xbuf = point.getX().toBuffer({ size: 32 }); + var ybuf = point.getY().toBuffer({ size: 32 }); + + var prefix; + var odd = ybuf[ybuf.length - 1] % 2; + if (odd) { + prefix = Buffer.from([0x03]); + } else { + prefix = Buffer.from([0x02]); + } + return BufferUtil.concat([prefix, xbuf]); +}; + +export default Point; diff --git a/src/backend/wallet/lib/crypto/random.js b/src/backend/wallet/lib/crypto/random.js new file mode 100644 index 0000000..2a15940 --- /dev/null +++ b/src/backend/wallet/lib/crypto/random.js @@ -0,0 +1,56 @@ +'use strict'; +import crypto from 'crypto'; +function Random() { +} + +/* secure random bytes that sometimes throws an error due to lack of entropy */ +Random.getRandomBuffer = function (size) { + if (process.browser) + return Random.getRandomBufferBrowser(size); + else + return Random.getRandomBufferNode(size); +}; + +Random.getRandomBufferNode = function (size) { + return crypto.randomBytes(size); +}; + +Random.getRandomBufferBrowser = function (size) { + if (!window.crypto && !window.msCrypto) + throw new Error('window.crypto not available'); + + if (window.crypto && window.crypto.getRandomValues) + var crypto = window.crypto; + else if (window.msCrypto && window.msCrypto.getRandomValues) //internet explorer + var crypto = window.msCrypto; + else + throw new Error('window.crypto.getRandomValues not available'); + + var bbuf = new Uint8Array(size); + crypto.getRandomValues(bbuf); + var buf = Buffer.from(bbuf); + + return buf; +}; + +/* insecure random bytes, but it never fails */ +Random.getPseudoRandomBuffer = function (size) { + var b32 = 0x100000000; + var b = Buffer.alloc(size); + var r; + + for (var i = 0; i <= size; i++) { + var j = Math.floor(i / 4); + var k = i - j * 4; + if (k === 0) { + r = Math.random() * b32; + b[i] = r & 0xff; + } else { + b[i] = (r = r >>> 8) & 0xff; + } + } + + return b; +}; + +export default Random; diff --git a/src/backend/wallet/lib/crypto/signature.js b/src/backend/wallet/lib/crypto/signature.js new file mode 100644 index 0000000..6ef8776 --- /dev/null +++ b/src/backend/wallet/lib/crypto/signature.js @@ -0,0 +1,313 @@ +'use strict'; + +import BN from './bn'; +import _ from 'lodash'; +import $ from '../util/preconditions'; +import BufferUtil from '../util/buffer'; +import JSUtil from '../util/js'; + +var Signature = function Signature(r, s) { + if (!(this instanceof Signature)) { + return new Signature(r, s); + } + if (r instanceof BN) { + this.set({ + r: r, + s: s + }); + } else if (r) { + var obj = r; + this.set(obj); + } +}; + +/* jshint maxcomplexity: 7 */ +Signature.prototype.set = function (obj) { + this.r = obj.r || this.r || undefined; + this.s = obj.s || this.s || undefined; + + this.i = typeof obj.i !== 'undefined' ? obj.i : this.i; //public key recovery parameter in range [0, 3] + this.compressed = typeof obj.compressed !== 'undefined' ? + obj.compressed : this.compressed; //whether the recovered pubkey is compressed + this.nhashtype = obj.nhashtype || this.nhashtype || undefined; + return this; +}; + +Signature.fromCompact = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf), 'Argument is expected to be a Buffer'); + + var sig = new Signature(); + + var compressed = true; + var i = buf.slice(0, 1)[0] - 27 - 4; + if (i < 0) { + compressed = false; + i = i + 4; + } + + var b2 = buf.slice(1, 33); + var b3 = buf.slice(33, 65); + + $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be 0, 1, 2, or 3')); + $.checkArgument(b2.length === 32, new Error('r must be 32 bytes')); + $.checkArgument(b3.length === 32, new Error('s must be 32 bytes')); + + sig.compressed = compressed; + sig.i = i; + sig.r = BN.fromBuffer(b2); + sig.s = BN.fromBuffer(b3); + + return sig; +}; + +Signature.fromDER = Signature.fromBuffer = function (buf, strict) { + var obj = Signature.parseDER(buf, strict); + var sig = new Signature(); + + sig.r = obj.r; + sig.s = obj.s; + + return sig; +}; + +// The format used in a tx +Signature.fromTxFormat = function (buf) { + var nhashtype = buf.readUInt8(buf.length - 1); + var derbuf = buf.slice(0, buf.length - 1); + var sig = new Signature.fromDER(derbuf, false); + sig.nhashtype = nhashtype; + return sig; +}; + +Signature.fromString = function (str) { + var buf = Buffer.from(str, 'hex'); + return Signature.fromDER(buf); +}; + + +/** + * In order to mimic the non-strict DER encoding of OpenSSL, set strict = false. + */ +Signature.parseDER = function (buf, strict) { + $.checkArgument(BufferUtil.isBuffer(buf), new Error('DER formatted signature should be a buffer')); + if (_.isUndefined(strict)) { + strict = true; + } + + var header = buf[0]; + $.checkArgument(header === 0x30, new Error('Header byte should be 0x30')); + + var length = buf[1]; + var buflength = buf.slice(2).length; + $.checkArgument(!strict || length === buflength, new Error('Length byte should length of what follows')); + + length = length < buflength ? length : buflength; + + var rheader = buf[2 + 0]; + $.checkArgument(rheader === 0x02, new Error('Integer byte for r should be 0x02')); + + var rlength = buf[2 + 1]; + var rbuf = buf.slice(2 + 2, 2 + 2 + rlength); + var r = BN.fromBuffer(rbuf); + var rneg = buf[2 + 1 + 1] === 0x00 ? true : false; + $.checkArgument(rlength === rbuf.length, new Error('Length of r incorrect')); + + var sheader = buf[2 + 2 + rlength + 0]; + $.checkArgument(sheader === 0x02, new Error('Integer byte for s should be 0x02')); + + var slength = buf[2 + 2 + rlength + 1]; + var sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); + var s = BN.fromBuffer(sbuf); + var sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false; + $.checkArgument(slength === sbuf.length, new Error('Length of s incorrect')); + + var sumlength = 2 + 2 + rlength + 2 + slength; + $.checkArgument(length === sumlength - 2, new Error('Length of signature incorrect')); + + var obj = { + header: header, + length: length, + rheader: rheader, + rlength: rlength, + rneg: rneg, + rbuf: rbuf, + r: r, + sheader: sheader, + slength: slength, + sneg: sneg, + sbuf: sbuf, + s: s + }; + + return obj; +}; + + +Signature.prototype.toCompact = function (i, compressed) { + i = typeof i === 'number' ? i : this.i; + compressed = typeof compressed === 'boolean' ? compressed : this.compressed; + + if (!(i === 0 || i === 1 || i === 2 || i === 3)) { + throw new Error('i must be equal to 0, 1, 2, or 3'); + } + + var val = i + 27 + 4; + if (compressed === false) { + val = val - 4; + } + var b1 = Buffer.from([val]); + var b2 = this.r.toBuffer({ + size: 32 + }); + var b3 = this.s.toBuffer({ + size: 32 + }); + return Buffer.concat([b1, b2, b3]); +}; + +Signature.prototype.toBuffer = Signature.prototype.toDER = function () { + var rnbuf = this.r.toBuffer(); + var snbuf = this.s.toBuffer(); + + var rneg = rnbuf[0] & 0x80 ? true : false; + var sneg = snbuf[0] & 0x80 ? true : false; + + var rbuf = rneg ? Buffer.concat([Buffer.from([0x00]), rnbuf]) : rnbuf; + var sbuf = sneg ? Buffer.concat([Buffer.from([0x00]), snbuf]) : snbuf; + + var rlength = rbuf.length; + var slength = sbuf.length; + var length = 2 + rlength + 2 + slength; + var rheader = 0x02; + var sheader = 0x02; + var header = 0x30; + + var der = Buffer.concat([Buffer.from([header, length, rheader, rlength]), rbuf, Buffer.from([sheader, slength]), sbuf]); + return der; +}; + +Signature.prototype.toString = function () { + var buf = this.toDER(); + return buf.toString('hex'); +}; + +/** + * This function is translated from bitcoind's IsDERSignature and is used in + * the script interpreter. This "DER" format actually includes an extra byte, + * the nhashtype, at the end. It is really the tx format, not DER format. + * + * A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype] + * Where R and S are not negative (their first byte has its highest bit not set), and not + * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, + * in which case a single 0 byte is necessary and even required). + * + * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 + */ +Signature.isTxDER = function (buf) { + if (buf.length < 9) { + // Non-canonical signature: too short + return false; + } + if (buf.length > 73) { + // Non-canonical signature: too long + return false; + } + if (buf[0] !== 0x30) { + // Non-canonical signature: wrong type + return false; + } + if (buf[1] !== buf.length - 3) { + // Non-canonical signature: wrong length marker + return false; + } + var nLenR = buf[3]; + if (5 + nLenR >= buf.length) { + // Non-canonical signature: S length misplaced + return false; + } + var nLenS = buf[5 + nLenR]; + if ((nLenR + nLenS + 7) !== buf.length) { + // Non-canonical signature: R+S length mismatch + return false; + } + + var R = buf.slice(4); + if (buf[4 - 2] !== 0x02) { + // Non-canonical signature: R value type mismatch + return false; + } + if (nLenR === 0) { + // Non-canonical signature: R length is zero + return false; + } + if (R[0] & 0x80) { + // Non-canonical signature: R value negative + return false; + } + if (nLenR > 1 && (R[0] === 0x00) && !(R[1] & 0x80)) { + // Non-canonical signature: R value excessively padded + return false; + } + + var S = buf.slice(6 + nLenR); + if (buf[6 + nLenR - 2] !== 0x02) { + // Non-canonical signature: S value type mismatch + return false; + } + if (nLenS === 0) { + // Non-canonical signature: S length is zero + return false; + } + if (S[0] & 0x80) { + // Non-canonical signature: S value negative + return false; + } + if (nLenS > 1 && (S[0] === 0x00) && !(S[1] & 0x80)) { + // Non-canonical signature: S value excessively padded + return false; + } + return true; +}; + +/** + * Compares to bitcoind's IsLowDERSignature + * See also ECDSA signature algorithm which enforces this. + * See also BIP 62, "low S values in signatures" + */ +Signature.prototype.hasLowS = function () { + if (this.s.lt(new BN(1)) || + this.s.gt(new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))) { + return false; + } + return true; +}; + +/** + * @returns true if the nhashtype is exactly equal to one of the standard options or combinations thereof. + * Translated from bitcoind's IsDefinedHashtypeSignature + */ +Signature.prototype.hasDefinedHashtype = function () { + if (!JSUtil.isNaturalNumber(this.nhashtype)) { + return false; + } + // accept with or without Signature.SIGHASH_ANYONECANPAY by ignoring the bit + var temp = this.nhashtype & ~Signature.SIGHASH_ANYONECANPAY; + if (temp < Signature.SIGHASH_ALL || temp > Signature.SIGHASH_SINGLE) { + return false; + } + return true; +}; + +Signature.prototype.toTxFormat = function () { + var derbuf = this.toDER(); + var buf = Buffer.alloc(1); + buf.writeUInt8(this.nhashtype, 0); + return Buffer.concat([derbuf, buf]); +}; + +Signature.SIGHASH_ALL = 0x01; +Signature.SIGHASH_NONE = 0x02; +Signature.SIGHASH_SINGLE = 0x03; +Signature.SIGHASH_ANYONECANPAY = 0x80; + +export default Signature; diff --git a/src/backend/wallet/lib/encoding/base58.js b/src/backend/wallet/lib/encoding/base58.js new file mode 100644 index 0000000..9162fee --- /dev/null +++ b/src/backend/wallet/lib/encoding/base58.js @@ -0,0 +1,70 @@ +'use strict'; + +import _ from 'lodash'; +import bs58 from 'bs58'; +import buffer from 'buffer'; + +var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.split(''); + +var Base58 = function Base58(obj) { + /* jshint maxcomplexity: 8 */ + if (!(this instanceof Base58)) { + return new Base58(obj); + } + if (Buffer.isBuffer(obj)) { + var buf = obj; + this.fromBuffer(buf); + } else if (typeof obj === 'string') { + var str = obj; + this.fromString(str); + } else if (obj) { + this.set(obj); + } +}; + +Base58.validCharacters = function validCharacters(chars) { + if (buffer.Buffer && buffer.Buffer.isBuffer(chars)) { + chars = chars.toString(); + } + return _.every(_.map(chars, function (char) { return _.includes(ALPHABET, char); })); +}; + +Base58.prototype.set = function (obj) { + this.buf = obj.buf || this.buf || undefined; + return this; +}; + +Base58.encode = function (buf) { + if (buffer.Buffer && !buffer.Buffer.isBuffer(buf)) { + throw new Error('Input should be a buffer'); + } + return bs58.encode(buf); +}; + +Base58.decode = function (str) { + if (typeof str !== 'string') { + throw new Error('Input should be a string'); + } + return Buffer.from(bs58.decode(str)); +}; + +Base58.prototype.fromBuffer = function (buf) { + this.buf = buf; + return this; +}; + +Base58.prototype.fromString = function (str) { + var buf = Base58.decode(str); + this.buf = buf; + return this; +}; + +Base58.prototype.toBuffer = function () { + return this.buf; +}; + +Base58.prototype.toString = function () { + return Base58.encode(this.buf); +}; + +export default Base58; diff --git a/src/backend/wallet/lib/encoding/base58check.js b/src/backend/wallet/lib/encoding/base58check.js new file mode 100644 index 0000000..a8a2b58 --- /dev/null +++ b/src/backend/wallet/lib/encoding/base58check.js @@ -0,0 +1,95 @@ +'use strict'; + +import _ from 'lodash'; +import Base58 from './base58'; +import buffer from 'buffer'; +import { sha256sha256 } from '../crypto/hash'; + +var Base58Check = function Base58Check(obj) { + if (!(this instanceof Base58Check)) + return new Base58Check(obj); + if (Buffer.isBuffer(obj)) { + var buf = obj; + this.fromBuffer(buf); + } else if (typeof obj === 'string') { + var str = obj; + this.fromString(str); + } else if (obj) { + this.set(obj); + } +}; + +Base58Check.prototype.set = function (obj) { + this.buf = obj.buf || this.buf || undefined; + return this; +}; + +Base58Check.validChecksum = function validChecksum(data, checksum) { + if (_.isString(data)) { + data = new buffer.Buffer(Base58.decode(data)); + } + if (_.isString(checksum)) { + checksum = new buffer.Buffer(Base58.decode(checksum)); + } + if (!checksum) { + checksum = data.slice(-4); + data = data.slice(0, -4); + } + return Base58Check.checksum(data).toString('hex') === checksum.toString('hex'); +}; + +Base58Check.decode = function (s) { + if (typeof s !== 'string') + throw new Error('Input must be a string'); + + var buf = Buffer.from(Base58.decode(s)); + + if (buf.length < 4) + throw new Error("Input string too short"); + + var data = buf.slice(0, -4); + var csum = buf.slice(-4); + + var hash = sha256sha256(data); + var hash4 = hash.slice(0, 4); + + if (csum.toString('hex') !== hash4.toString('hex')) + throw new Error("Checksum mismatch"); + + return data; +}; + +Base58Check.checksum = function (buffer) { + return sha256sha256(buffer).slice(0, 4); +}; + +Base58Check.encode = function (buf) { + if (!Buffer.isBuffer(buf)) + throw new Error('Input must be a buffer'); + var checkedBuf = Buffer.alloc(buf.length + 4); + var hash = Base58Check.checksum(buf); + buf.copy(checkedBuf); + hash.copy(checkedBuf, buf.length); + return Base58.encode(checkedBuf); +}; + +Base58Check.prototype.fromBuffer = function (buf) { + this.buf = buf; + return this; +}; + +Base58Check.prototype.fromString = function (str) { + var buf = Base58Check.decode(str); + this.buf = buf; + return this; +}; + +Base58Check.prototype.toBuffer = function () { + return this.buf; +}; + +Base58Check.prototype.toString = function () { + return Base58Check.encode(this.buf); +}; + +export default Base58Check; diff --git a/src/backend/wallet/lib/encoding/bufferreader.js b/src/backend/wallet/lib/encoding/bufferreader.js new file mode 100644 index 0000000..69e194e --- /dev/null +++ b/src/backend/wallet/lib/encoding/bufferreader.js @@ -0,0 +1,198 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import BufferUtil from '../util/buffer'; +import BN from '../crypto/bn'; + +var BufferReader = function BufferReader(buf) { + if (!(this instanceof BufferReader)) { + return new BufferReader(buf); + } + if (_.isUndefined(buf)) { + return; + } + if (Buffer.isBuffer(buf)) { + this.set({ + buf: buf + }); + } else if (_.isString(buf)) { + this.set({ + buf: Buffer.from(buf, 'hex'), + }); + } else if (_.isObject(buf)) { + var obj = buf; + this.set(obj); + } else { + throw new TypeError('Unrecognized argument for BufferReader'); + } +}; + +BufferReader.prototype.set = function (obj) { + this.buf = obj.buf || this.buf || undefined; + this.pos = obj.pos || this.pos || 0; + return this; +}; + +BufferReader.prototype.eof = function () { + return this.pos >= this.buf.length; +}; + +BufferReader.prototype.finished = BufferReader.prototype.eof; + +BufferReader.prototype.read = function (len) { + $.checkArgument(!_.isUndefined(len), 'Must specify a length'); + var buf = this.buf.slice(this.pos, this.pos + len); + this.pos = this.pos + len; + return buf; +}; + +BufferReader.prototype.readAll = function () { + var buf = this.buf.slice(this.pos, this.buf.length); + this.pos = this.buf.length; + return buf; +}; + +BufferReader.prototype.readUInt8 = function () { + var val = this.buf.readUInt8(this.pos); + this.pos = this.pos + 1; + return val; +}; + +BufferReader.prototype.readUInt16BE = function () { + var val = this.buf.readUInt16BE(this.pos); + this.pos = this.pos + 2; + return val; +}; + +BufferReader.prototype.readUInt16LE = function () { + var val = this.buf.readUInt16LE(this.pos); + this.pos = this.pos + 2; + return val; +}; + +BufferReader.prototype.readUInt32BE = function () { + var val = this.buf.readUInt32BE(this.pos); + this.pos = this.pos + 4; + return val; +}; + +BufferReader.prototype.readUInt32LE = function () { + var val = this.buf.readUInt32LE(this.pos); + this.pos = this.pos + 4; + return val; +}; + +BufferReader.prototype.readInt32LE = function () { + var val = this.buf.readInt32LE(this.pos); + this.pos = this.pos + 4; + return val; +}; + +BufferReader.prototype.readUInt64BEBN = function () { + var buf = this.buf.slice(this.pos, this.pos + 8); + var bn = BN.fromBuffer(buf); + this.pos = this.pos + 8; + return bn; +}; + +BufferReader.prototype.readUInt64LEBN = function () { + var second = this.buf.readUInt32LE(this.pos); + var first = this.buf.readUInt32LE(this.pos + 4); + var combined = (first * 0x100000000) + second; + // Instantiating an instance of BN with a number is faster than with an + // array or string. However, the maximum safe number for a double precision + // floating point is 2 ^ 52 - 1 (0x1fffffffffffff), thus we can safely use + // non-floating point numbers less than this amount (52 bits). And in the case + // that the number is larger, we can instatiate an instance of BN by passing + // an array from the buffer (slower) and specifying the endianness. + var bn; + if (combined <= 0x1fffffffffffff) { + bn = new BN(combined); + } else { + var data = Array.prototype.slice.call(this.buf, this.pos, this.pos + 8); + bn = new BN(data, 10, 'le'); + } + this.pos = this.pos + 8; + return bn; +}; + +BufferReader.prototype.readVarintNum = function () { + var first = this.readUInt8(); + switch (first) { + case 0xFD: + return this.readUInt16LE(); + case 0xFE: + return this.readUInt32LE(); + case 0xFF: + var bn = this.readUInt64LEBN(); + var n = bn.toNumber(); + if (n <= Math.pow(2, 53)) { + return n; + } else { + throw new Error('number too large to retain precision - use readVarintBN'); + } + break; + default: + return first; + } +}; + +/** + * reads a length prepended buffer + */ +BufferReader.prototype.readVarLengthBuffer = function () { + var len = this.readVarintNum(); + var buf = this.read(len); + $.checkState(buf.length === len, 'Invalid length while reading varlength buffer. ' + + 'Expected to read: ' + len + ' and read ' + buf.length); + return buf; +}; + +BufferReader.prototype.readVarintBuf = function () { + var first = this.buf.readUInt8(this.pos); + switch (first) { + case 0xFD: + return this.read(1 + 2); + case 0xFE: + return this.read(1 + 4); + case 0xFF: + return this.read(1 + 8); + default: + return this.read(1); + } +}; + +BufferReader.prototype.readVarintBN = function () { + var first = this.readUInt8(); + switch (first) { + case 0xFD: + return new BN(this.readUInt16LE()); + case 0xFE: + return new BN(this.readUInt32LE()); + case 0xFF: + return this.readUInt64LEBN(); + default: + return new BN(first); + } +}; + +BufferReader.prototype.reverse = function () { + var buf = Buffer.alloc(this.buf.length); + for (var i = 0; i < buf.length; i++) { + buf[i] = this.buf[this.buf.length - 1 - i]; + } + this.buf = buf; + return this; +}; + +BufferReader.prototype.readReverse = function (len) { + if (_.isUndefined(len)) { + len = this.buf.length; + } + var buf = this.buf.slice(this.pos, this.pos + len); + this.pos = this.pos + len; + return BufferUtil.reverse(buf); +}; + +export default BufferReader; diff --git a/src/backend/wallet/lib/encoding/bufferwriter.js b/src/backend/wallet/lib/encoding/bufferwriter.js new file mode 100644 index 0000000..43c074f --- /dev/null +++ b/src/backend/wallet/lib/encoding/bufferwriter.js @@ -0,0 +1,165 @@ +'use strict'; + +import bufferUtil from '../util/buffer'; +import assert from 'assert'; + +var BufferWriter = function BufferWriter(obj) { + if (!(this instanceof BufferWriter)) + return new BufferWriter(obj); + this.bufLen = 0; + if (obj) + this.set(obj); + else + this.bufs = []; +}; + +BufferWriter.prototype.set = function (obj) { + this.bufs = obj.bufs || this.bufs || []; + this.bufLen = this.bufs.reduce(function (prev, buf) { return prev + buf.length; }, 0); + return this; +}; + +BufferWriter.prototype.toBuffer = function () { + return this.concat(); +}; + +BufferWriter.prototype.concat = function () { + return Buffer.concat(this.bufs, this.bufLen); +}; + +BufferWriter.prototype.write = function (buf) { + assert(bufferUtil.isBuffer(buf)); + this.bufs.push(buf); + this.bufLen += buf.length; + return this; +}; + +BufferWriter.prototype.writeReverse = function (buf) { + assert(bufferUtil.isBuffer(buf)); + this.bufs.push(bufferUtil.reverse(buf)); + this.bufLen += buf.length; + return this; +}; + +BufferWriter.prototype.writeString = function (str) { + var buf = Buffer.from(str) + var len = buf.length + this.writeVarintNum(len) + if (len > 0) { + this.write(buf) + } + return this; +}; + +BufferWriter.prototype.writeUInt8 = function (n) { + var buf = Buffer.alloc(1); + buf.writeUInt8(n, 0); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeUInt16BE = function (n) { + var buf = Buffer.alloc(2); + buf.writeUInt16BE(n, 0); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeUInt16LE = function (n) { + var buf = Buffer.alloc(2); + buf.writeUInt16LE(n, 0); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeUInt32BE = function (n) { + var buf = Buffer.alloc(4); + buf.writeUInt32BE(n, 0); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeInt32LE = function (n) { + var buf = Buffer.alloc(4); + buf.writeInt32LE(n, 0); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeUInt32LE = function (n) { + var buf = Buffer.alloc(4); + buf.writeUInt32LE(n, 0); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeUInt64BEBN = function (bn) { + var buf = bn.toBuffer({ size: 8 }); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeUInt64LEBN = function (bn) { + var buf = bn.toBuffer({ size: 8 }); + this.writeReverse(buf); + return this; +}; + +BufferWriter.prototype.writeVarintNum = function (n) { + var buf = BufferWriter.varintBufNum(n); + this.write(buf); + return this; +}; + +BufferWriter.prototype.writeVarintBN = function (bn) { + var buf = BufferWriter.varintBufBN(bn); + this.write(buf); + return this; +}; + +BufferWriter.varintBufNum = function (n) { + var buf = undefined; + if (n < 253) { + buf = Buffer.alloc(1); + buf.writeUInt8(n, 0); + } else if (n < 0x10000) { + buf = Buffer.alloc(1 + 2); + buf.writeUInt8(253, 0); + buf.writeUInt16LE(n, 1); + } else if (n < 0x100000000) { + buf = Buffer.alloc(1 + 4); + buf.writeUInt8(254, 0); + buf.writeUInt32LE(n, 1); + } else { + buf = Buffer.alloc(1 + 8); + buf.writeUInt8(255, 0); + buf.writeInt32LE(n & -1, 1); + buf.writeUInt32LE(Math.floor(n / 0x100000000), 5); + } + return buf; +}; + +BufferWriter.varintBufBN = function (bn) { + var buf = undefined; + var n = bn.toNumber(); + if (n < 253) { + buf = Buffer.alloc(1); + buf.writeUInt8(n, 0); + } else if (n < 0x10000) { + buf = Buffer.alloc(1 + 2); + buf.writeUInt8(253, 0); + buf.writeUInt16LE(n, 1); + } else if (n < 0x100000000) { + buf = Buffer.alloc(1 + 4); + buf.writeUInt8(254, 0); + buf.writeUInt32LE(n, 1); + } else { + var bw = new BufferWriter(); + bw.writeUInt8(255); + bw.writeUInt64LEBN(bn); + var buf = bw.concat(); + } + return buf; +}; + +export default BufferWriter; diff --git a/src/backend/wallet/lib/encoding/varint.js b/src/backend/wallet/lib/encoding/varint.js new file mode 100644 index 0000000..35ae714 --- /dev/null +++ b/src/backend/wallet/lib/encoding/varint.js @@ -0,0 +1,72 @@ +'use strict'; + +import BufferWriter from './bufferwriter'; +import BufferReader from './bufferreader'; +import BN from '../crypto/bn'; + +var Varint = function Varint(buf) { + if (!(this instanceof Varint)) + return new Varint(buf); + if (Buffer.isBuffer(buf)) { + this.buf = buf; + } else if (typeof buf === 'number') { + var num = buf; + this.fromNumber(num); + } else if (buf instanceof BN) { + var bn = buf; + this.fromBN(bn); + } else if (buf) { + var obj = buf; + this.set(obj); + } +}; + +Varint.prototype.set = function (obj) { + this.buf = obj.buf || this.buf; + return this; +}; + +Varint.prototype.fromString = function (str) { + this.set({ + buf: Buffer.from(str, 'hex') + }); + return this; +}; + +Varint.prototype.toString = function () { + return this.buf.toString('hex'); +}; + +Varint.prototype.fromBuffer = function (buf) { + this.buf = buf; + return this; +}; + +Varint.prototype.fromBufferReader = function (br) { + this.buf = br.readVarintBuf(); + return this; +}; + +Varint.prototype.fromBN = function (bn) { + this.buf = BufferWriter().writeVarintBN(bn).concat(); + return this; +}; + +Varint.prototype.fromNumber = function (num) { + this.buf = BufferWriter().writeVarintNum(num).concat(); + return this; +}; + +Varint.prototype.toBuffer = function () { + return this.buf; +}; + +Varint.prototype.toBN = function () { + return BufferReader(this.buf).readVarintBN(); +}; + +Varint.prototype.toNumber = function () { + return BufferReader(this.buf).readVarintNum(); +}; + +export default Varint; diff --git a/src/backend/wallet/lib/errors.js b/src/backend/wallet/lib/errors.js new file mode 100644 index 0000000..c763492 --- /dev/null +++ b/src/backend/wallet/lib/errors.js @@ -0,0 +1,18 @@ +'use strict'; + +var spec = { + name: 'Mnemonic', + message: 'Internal Error on bitcore-mnemonic module {0}', + errors: [{ + name: 'InvalidEntropy', + message: 'Entropy length must be an even multiple of 11 bits: {0}' + }, { + name: 'UnknownWordlist', + message: 'Could not detect the used word list: {0}' + }, { + name: 'InvalidMnemonic', + message: 'Mnemonic string is invalid: {0}' + }] +}; + +export default spec; diff --git a/src/backend/wallet/lib/errors/index.js b/src/backend/wallet/lib/errors/index.js new file mode 100644 index 0000000..dc9d99e --- /dev/null +++ b/src/backend/wallet/lib/errors/index.js @@ -0,0 +1,61 @@ +'use strict'; + +import _ from 'lodash'; + +function format(message, args) { + return message + .replace('{0}', args[0]) + .replace('{1}', args[1]) + .replace('{2}', args[2]); +} +var traverseNode = function (parent, errorDefinition) { + var NodeError = function () { + if (_.isString(errorDefinition.message)) { + this.message = format(errorDefinition.message, arguments); + } else if (_.isFunction(errorDefinition.message)) { + this.message = errorDefinition.message.apply(null, arguments); + } else { + throw new Error('Invalid error definition for ' + errorDefinition.name); + } + this.stack = this.message + '\n' + (new Error()).stack; + }; + NodeError.prototype = Object.create(parent.prototype); + NodeError.prototype.name = parent.prototype.name + errorDefinition.name; + parent[errorDefinition.name] = NodeError; + if (errorDefinition.errors) { + childDefinitions(NodeError, errorDefinition.errors); + } + return NodeError; +}; + +/* jshint latedef: false */ +var childDefinitions = function (parent, childDefinitions) { + _.each(childDefinitions, function (childDefinition) { + traverseNode(parent, childDefinition); + }); +}; +/* jshint latedef: true */ + +var traverseRoot = function (parent, errorsDefinition) { + childDefinitions(parent, errorsDefinition); + return parent; +}; + + +var bitcore = {}; +bitcore.Error = function () { + this.message = 'Internal error'; + this.stack = this.message + '\n' + (new Error()).stack; +}; +bitcore.Error.prototype = Object.create(Error.prototype); +bitcore.Error.prototype.name = 'bitcore.Error'; + + +import data from './spec'; +traverseRoot(bitcore.Error, data); + +export default bitcore.Error; + +export const extend = function (spec) { + return traverseNode(bitcore.Error, spec); +}; diff --git a/src/backend/wallet/lib/errors/spec.js b/src/backend/wallet/lib/errors/spec.js new file mode 100644 index 0000000..971de18 --- /dev/null +++ b/src/backend/wallet/lib/errors/spec.js @@ -0,0 +1,184 @@ +'use strict'; + +var docsURL = 'http://bitcore.io/'; + +export default [{ + name: 'InvalidB58Char', + message: 'Invalid Base58 character: {0} in {1}' +}, { + name: 'InvalidB58Checksum', + message: 'Invalid Base58 checksum for {0}' +}, { + name: 'InvalidNetwork', + message: 'Invalid version for network: got {0}' +}, { + name: 'InvalidState', + message: 'Invalid state: {0}' +}, { + name: 'NotImplemented', + message: 'Function {0} was not implemented yet' +}, { + name: 'InvalidNetworkArgument', + message: 'Invalid network: must be "livenet" or "testnet", got {0}' +}, { + name: 'InvalidArgument', + message: function () { + return 'Invalid Argument' + (arguments[0] ? (': ' + arguments[0]) : '') + + (arguments[1] ? (' Documentation: ' + docsURL + arguments[1]) : ''); + } +}, { + name: 'AbstractMethodInvoked', + message: 'Abstract Method Invocation: {0}' +}, { + name: 'InvalidArgumentType', + message: function () { + return 'Invalid Argument for ' + arguments[2] + ', expected ' + arguments[1] + ' but got ' + typeof arguments[0]; + } +}, { + name: 'Unit', + message: 'Internal Error on Unit {0}', + errors: [{ + 'name': 'UnknownCode', + 'message': 'Unrecognized unit code: {0}' + }, { + 'name': 'InvalidRate', + 'message': 'Invalid exchange rate: {0}' + }] +}, { + name: 'MerkleBlock', + message: 'Internal Error on MerkleBlock {0}', + errors: [{ + 'name': 'InvalidMerkleTree', + 'message': 'This MerkleBlock contain an invalid Merkle Tree' + }] +}, { + name: 'Transaction', + message: 'Internal Error on Transaction {0}', + errors: [{ + name: 'Input', + message: 'Internal Error on Input {0}', + errors: [{ + name: 'MissingScript', + message: 'Need a script to create an input' + }, { + name: 'UnsupportedScript', + message: 'Unsupported input script type: {0}' + }, { + name: 'MissingPreviousOutput', + message: 'No previous output information.' + }] + }, { + name: 'NeedMoreInfo', + message: '{0}' + }, { + name: 'InvalidSorting', + message: 'The sorting function provided did not return the change output as one of the array elements' + }, { + name: 'InvalidOutputAmountSum', + message: '{0}' + }, { + name: 'MissingSignatures', + message: 'Some inputs have not been fully signed' + }, { + name: 'InvalidIndex', + message: 'Invalid index: {0} is not between 0, {1}' + }, { + name: 'UnableToVerifySignature', + message: 'Unable to verify signature: {0}' + }, { + name: 'DustOutputs', + message: 'Dust amount detected in one output' + }, { + name: 'InvalidSatoshis', + message: 'Output satoshis are invalid', + }, { + name: 'FeeError', + message: 'Internal Error on Fee {0}', + errors: [{ + name: 'TooSmall', + message: 'Fee is too small: {0}', + }, { + name: 'TooLarge', + message: 'Fee is too large: {0}', + }, { + name: 'Different', + message: 'Unspent value is different from specified fee: {0}', + }] + }, { + name: 'ChangeAddressMissing', + message: 'Change address is missing' + }, { + name: 'BlockHeightTooHigh', + message: 'Block Height can be at most 2^32 -1' + }, { + name: 'NLockTimeOutOfRange', + message: 'Block Height can only be between 0 and 499 999 999' + }, { + name: 'LockTimeTooEarly', + message: 'Lock Time can\'t be earlier than UNIX date 500 000 000' + }] +}, { + name: 'Script', + message: 'Internal Error on Script {0}', + errors: [{ + name: 'UnrecognizedAddress', + message: 'Expected argument {0} to be an address' + }, { + name: 'CantDeriveAddress', + message: 'Can\'t derive address associated with script {0}, needs to be p2pkh in, p2pkh out, p2sh in, or p2sh out.' + }, { + name: 'InvalidBuffer', + message: 'Invalid script buffer: can\'t parse valid script from given buffer {0}' + }] +}, { + name: 'HDPrivateKey', + message: 'Internal Error on HDPrivateKey {0}', + errors: [{ + name: 'InvalidDerivationArgument', + message: 'Invalid derivation argument {0}, expected string, or number and boolean' + }, { + name: 'InvalidEntropyArgument', + message: 'Invalid entropy: must be an hexa string or binary buffer, got {0}', + errors: [{ + name: 'TooMuchEntropy', + message: 'Invalid entropy: more than 512 bits is non standard, got "{0}"' + }, { + name: 'NotEnoughEntropy', + message: 'Invalid entropy: at least 128 bits needed, got "{0}"' + }] + }, { + name: 'InvalidLength', + message: 'Invalid length for xprivkey string in {0}' + }, { + name: 'InvalidPath', + message: 'Invalid derivation path: {0}' + }, { + name: 'UnrecognizedArgument', + message: 'Invalid argument: creating a HDPrivateKey requires a string, buffer, json or object, got "{0}"' + }] +}, { + name: 'HDPublicKey', + message: 'Internal Error on HDPublicKey {0}', + errors: [{ + name: 'ArgumentIsPrivateExtended', + message: 'Argument is an extended private key: {0}' + }, { + name: 'InvalidDerivationArgument', + message: 'Invalid derivation argument: got {0}' + }, { + name: 'InvalidLength', + message: 'Invalid length for xpubkey: got "{0}"' + }, { + name: 'InvalidPath', + message: 'Invalid derivation path, it should look like: "m/1/100", got "{0}"' + }, { + name: 'InvalidIndexCantDeriveHardened', + message: 'Invalid argument: creating a hardened path requires an HDPrivateKey' + }, { + name: 'MustSupplyArgument', + message: 'Must supply an argument to create a HDPublicKey' + }, { + name: 'UnrecognizedArgument', + message: 'Invalid argument for creation, must be string, json, buffer, or object' + }] +}]; diff --git a/src/backend/wallet/lib/hdprivatekey.js b/src/backend/wallet/lib/hdprivatekey.js new file mode 100644 index 0000000..fe15b13 --- /dev/null +++ b/src/backend/wallet/lib/hdprivatekey.js @@ -0,0 +1,648 @@ +'use strict'; + + +import assert from 'assert'; +import buffer from 'buffer'; +import _ from 'lodash'; +import $ from './util/preconditions'; + +import BN from './crypto/bn'; +import Base58 from './encoding/base58'; +import Base58Check from './encoding/base58check'; +import Hash from './crypto/hash'; +import Network from './networks'; +import Point from './crypto/point'; +import PrivateKey from './privatekey'; +import Random from './crypto/random'; + +import errors from './errors'; +var hdErrors = errors.HDPrivateKey; +import BufferUtil from './util/buffer'; +import JSUtil from './util/js'; + +var MINIMUM_ENTROPY_BITS = 128; +var BITS_TO_BYTES = 1 / 8; +var MAXIMUM_ENTROPY_BITS = 512; + + +/** + * Represents an instance of an hierarchically derived private key. + * + * More info on https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + * + * @constructor + * @param {string|Buffer|Object} arg + */ +function HDPrivateKey(arg) { + /* jshint maxcomplexity: 10 */ + if (arg instanceof HDPrivateKey) { + return arg; + } + if (!(this instanceof HDPrivateKey)) { + return new HDPrivateKey(arg); + } + if (!arg) { + return this._generateRandomly(); + } + + if (Network.get(arg)) { + return this._generateRandomly(arg); + } else if (_.isString(arg) || BufferUtil.isBuffer(arg)) { + if (HDPrivateKey.isValidSerialized(arg)) { + this._buildFromSerialized(arg); + } else if (JSUtil.isValidJSON(arg)) { + this._buildFromJSON(arg); + } else if (BufferUtil.isBuffer(arg) && HDPrivateKey.isValidSerialized(arg.toString())) { + this._buildFromSerialized(arg.toString()); + } else { + throw HDPrivateKey.getSerializedError(arg); + } + } else if (_.isObject(arg)) { + this._buildFromObject(arg); + } else { + throw new hdErrors.UnrecognizedArgument(arg); + } +} + +/** + * Verifies that a given path is valid. + * + * @param {string|number} arg + * @param {boolean?} hardened + * @return {boolean} + */ +HDPrivateKey.isValidPath = function (arg, hardened) { + if (_.isString(arg)) { + var indexes = HDPrivateKey._getDerivationIndexes(arg); + return indexes !== null && _.every(indexes, HDPrivateKey.isValidPath); + } + + if (_.isNumber(arg)) { + if (arg < HDPrivateKey.Hardened && hardened === true) { + arg += HDPrivateKey.Hardened; + } + return arg >= 0 && arg < HDPrivateKey.MaxIndex; + } + + return false; +}; + +/** + * Internal function that splits a string path into a derivation index array. + * It will return null if the string path is malformed. + * It does not validate if indexes are in bounds. + * + * @param {string} path + * @return {Array} + */ +HDPrivateKey._getDerivationIndexes = function (path) { + var steps = path.split('/'); + + // Special cases: + if (_.includes(HDPrivateKey.RootElementAlias, path)) { + return []; + } + + if (!_.includes(HDPrivateKey.RootElementAlias, steps[0])) { + return null; + } + + var indexes = steps.slice(1).map(function (step) { + var isHardened = step.slice(-1) === '\''; + if (isHardened) { + step = step.slice(0, -1); + } + if (!step || step[0] === '-') { + return NaN; + } + var index = +step; // cast to number + if (isHardened) { + index += HDPrivateKey.Hardened; + } + + return index; + }); + + return _.some(indexes, isNaN) ? null : indexes; +}; + +/** + * WARNING: This method is deprecated. Use deriveChild or deriveNonCompliantChild instead. This is not BIP32 compliant + * + * + * Get a derived child based on a string or number. + * + * If the first argument is a string, it's parsed as the full path of + * derivation. Valid values for this argument include "m" (which returns the + * same private key), "m/0/1/40/2'/1000", where the ' quote means a hardened + * derivation. + * + * If the first argument is a number, the child with that index will be + * derived. If the second argument is truthy, the hardened version will be + * derived. See the example usage for clarification. + * + * @example + * ```javascript + * var parent = new HDPrivateKey('xprv...'); + * var child_0_1_2h = parent.derive(0).derive(1).derive(2, true); + * var copy_of_child_0_1_2h = parent.derive("m/0/1/2'"); + * assert(child_0_1_2h.xprivkey === copy_of_child_0_1_2h); + * ``` + * + * @param {string|number} arg + * @param {boolean?} hardened + */ +HDPrivateKey.prototype.derive = function (arg, hardened) { + return this.deriveNonCompliantChild(arg, hardened); +}; + +/** + * WARNING: This method will not be officially supported until v1.0.0. + * + * + * Get a derived child based on a string or number. + * + * If the first argument is a string, it's parsed as the full path of + * derivation. Valid values for this argument include "m" (which returns the + * same private key), "m/0/1/40/2'/1000", where the ' quote means a hardened + * derivation. + * + * If the first argument is a number, the child with that index will be + * derived. If the second argument is truthy, the hardened version will be + * derived. See the example usage for clarification. + * + * WARNING: The `nonCompliant` option should NOT be used, except for older implementation + * that used a derivation strategy that used a non-zero padded private key. + * + * @example + * ```javascript + * var parent = new HDPrivateKey('xprv...'); + * var child_0_1_2h = parent.deriveChild(0).deriveChild(1).deriveChild(2, true); + * var copy_of_child_0_1_2h = parent.deriveChild("m/0/1/2'"); + * assert(child_0_1_2h.xprivkey === copy_of_child_0_1_2h); + * ``` + * + * @param {string|number} arg + * @param {boolean?} hardened + */ +HDPrivateKey.prototype.deriveChild = function (arg, hardened) { + if (_.isNumber(arg)) { + return this._deriveWithNumber(arg, hardened); + } else if (_.isString(arg)) { + return this._deriveFromString(arg); + } else { + throw new hdErrors.InvalidDerivationArgument(arg); + } +}; + +/** + * WARNING: This method will not be officially supported until v1.0.0 + * + * + * WARNING: If this is a new implementation you should NOT use this method, you should be using + * `derive` instead. + * + * This method is explicitly for use and compatibility with an implementation that + * was not compliant with BIP32 regarding the derivation algorithm. The private key + * must be 32 bytes hashing, and this implementation will use the non-zero padded + * serialization of a private key, such that it's still possible to derive the privateKey + * to recover those funds. + * + * @param {string|number} arg + * @param {boolean?} hardened + */ +HDPrivateKey.prototype.deriveNonCompliantChild = function (arg, hardened) { + if (_.isNumber(arg)) { + return this._deriveWithNumber(arg, hardened, true); + } else if (_.isString(arg)) { + return this._deriveFromString(arg, true); + } else { + throw new hdErrors.InvalidDerivationArgument(arg); + } +}; + +HDPrivateKey.prototype._deriveWithNumber = function (index, hardened, nonCompliant) { + /* jshint maxstatements: 20 */ + /* jshint maxcomplexity: 10 */ + if (!HDPrivateKey.isValidPath(index, hardened)) { + throw new hdErrors.InvalidPath(index); + } + + hardened = index >= HDPrivateKey.Hardened ? true : hardened; + if (index < HDPrivateKey.Hardened && hardened === true) { + index += HDPrivateKey.Hardened; + } + + var indexBuffer = BufferUtil.integerAsBuffer(index); + var data; + if (hardened && nonCompliant) { + // The private key serialization in this case will not be exactly 32 bytes and can be + // any value less, and the value is not zero-padded. + var nonZeroPadded = this.privateKey.bn.toBuffer(); + data = BufferUtil.concat([new buffer.Buffer([0]), nonZeroPadded, indexBuffer]); + } else if (hardened) { + // This will use a 32 byte zero padded serialization of the private key + var privateKeyBuffer = this.privateKey.bn.toBuffer({ size: 32 }); + assert(privateKeyBuffer.length === 32, 'length of private key buffer is expected to be 32 bytes'); + data = BufferUtil.concat([new buffer.Buffer([0]), privateKeyBuffer, indexBuffer]); + } else { + data = BufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]); + } + var hash = Hash.sha512hmac(data, this._buffers.chainCode); + var leftPart = BN.fromBuffer(hash.slice(0, 32), { + size: 32 + }); + var chainCode = hash.slice(32, 64); + + var privateKey = leftPart.add(this.privateKey.toBigNumber()).umod(Point.getN()).toBuffer({ + size: 32 + }); + + if (!PrivateKey.isValid(privateKey)) { + // Index at this point is already hardened, we can pass null as the hardened arg + return this._deriveWithNumber(index + 1, null, nonCompliant); + } + + var derived = new HDPrivateKey({ + network: this.network, + depth: this.depth + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + privateKey: privateKey + }); + + return derived; +}; + +HDPrivateKey.prototype._deriveFromString = function (path, nonCompliant) { + if (!HDPrivateKey.isValidPath(path)) { + throw new hdErrors.InvalidPath(path); + } + + var indexes = HDPrivateKey._getDerivationIndexes(path); + var derived = indexes.reduce(function (prev, index) { + return prev._deriveWithNumber(index, null, nonCompliant); + }, this); + + return derived; +}; + +/** + * Verifies that a given serialized private key in base58 with checksum format + * is valid. + * + * @param {string|Buffer} data - the serialized private key + * @param {string|Network=} network - optional, if present, checks that the + * network provided matches the network serialized. + * @return {boolean} + */ +HDPrivateKey.isValidSerialized = function (data, network) { + return !HDPrivateKey.getSerializedError(data, network); +}; + +/** + * Checks what's the error that causes the validation of a serialized private key + * in base58 with checksum to fail. + * + * @param {string|Buffer} data - the serialized private key + * @param {string|Network=} network - optional, if present, checks that the + * network provided matches the network serialized. + * @return {errors.InvalidArgument|null} + */ +HDPrivateKey.getSerializedError = function (data, network) { + /* jshint maxcomplexity: 10 */ + if (!(_.isString(data) || BufferUtil.isBuffer(data))) { + return new hdErrors.UnrecognizedArgument('Expected string or buffer'); + } + if (!Base58.validCharacters(data)) { + return new errors.InvalidB58Char('(unknown)', data); + } + try { + data = Base58Check.decode(data); + } catch (e) { + return new errors.InvalidB58Checksum(data); + } + if (data.length !== HDPrivateKey.DataLength) { + return new hdErrors.InvalidLength(data); + } + if (!_.isUndefined(network)) { + var error = HDPrivateKey._validateNetwork(data, network); + if (error) { + return error; + } + } + return null; +}; + +HDPrivateKey._validateNetwork = function (data, networkArg) { + var network = Network.get(networkArg); + if (!network) { + return new errors.InvalidNetworkArgument(networkArg); + } + var version = data.slice(0, 4); + if (BufferUtil.integerFromBuffer(version) !== network.xprivkey) { + return new errors.InvalidNetwork(version); + } + return null; +}; + +HDPrivateKey.fromString = function (arg) { + $.checkArgument(_.isString(arg), 'No valid string was provided'); + return new HDPrivateKey(arg); +}; + +HDPrivateKey.fromObject = function (arg) { + $.checkArgument(_.isObject(arg), 'No valid argument was provided'); + return new HDPrivateKey(arg); +}; + +HDPrivateKey.prototype._buildFromJSON = function (arg) { + return this._buildFromObject(JSON.parse(arg)); +}; + +HDPrivateKey.prototype._buildFromObject = function (arg) { + /* jshint maxcomplexity: 12 */ + // TODO: Type validation + var buffers = { + version: arg.network ? BufferUtil.integerAsBuffer(Network.get(arg.network).xprivkey) : arg.version, + depth: _.isNumber(arg.depth) ? BufferUtil.integerAsSingleByteBuffer(arg.depth) : arg.depth, + parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? BufferUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint, + childIndex: _.isNumber(arg.childIndex) ? BufferUtil.integerAsBuffer(arg.childIndex) : arg.childIndex, + chainCode: _.isString(arg.chainCode) ? BufferUtil.hexToBuffer(arg.chainCode) : arg.chainCode, + privateKey: (_.isString(arg.privateKey) && JSUtil.isHexa(arg.privateKey)) ? BufferUtil.hexToBuffer(arg.privateKey) : arg.privateKey, + checksum: arg.checksum ? (arg.checksum.length ? arg.checksum : BufferUtil.integerAsBuffer(arg.checksum)) : undefined + }; + return this._buildFromBuffers(buffers); +}; + +HDPrivateKey.prototype._buildFromSerialized = function (arg) { + var decoded = Base58Check.decode(arg); + var buffers = { + version: decoded.slice(HDPrivateKey.VersionStart, HDPrivateKey.VersionEnd), + depth: decoded.slice(HDPrivateKey.DepthStart, HDPrivateKey.DepthEnd), + parentFingerPrint: decoded.slice(HDPrivateKey.ParentFingerPrintStart, + HDPrivateKey.ParentFingerPrintEnd), + childIndex: decoded.slice(HDPrivateKey.ChildIndexStart, HDPrivateKey.ChildIndexEnd), + chainCode: decoded.slice(HDPrivateKey.ChainCodeStart, HDPrivateKey.ChainCodeEnd), + privateKey: decoded.slice(HDPrivateKey.PrivateKeyStart, HDPrivateKey.PrivateKeyEnd), + checksum: decoded.slice(HDPrivateKey.ChecksumStart, HDPrivateKey.ChecksumEnd), + xprivkey: arg + }; + return this._buildFromBuffers(buffers); +}; + +HDPrivateKey.prototype._generateRandomly = function (network) { + return HDPrivateKey.fromSeed(Random.getRandomBuffer(64), network); +}; + +/** + * Generate a private key from a seed, as described in BIP32 + * + * @param {string|Buffer} hexa + * @param {*} network + * @return HDPrivateKey + */ +HDPrivateKey.fromSeed = function (hexa, network) { + /* jshint maxcomplexity: 8 */ + if (JSUtil.isHexaString(hexa)) { + hexa = BufferUtil.hexToBuffer(hexa); + } + if (!Buffer.isBuffer(hexa)) { + throw new hdErrors.InvalidEntropyArgument(hexa); + } + if (hexa.length < MINIMUM_ENTROPY_BITS * BITS_TO_BYTES) { + throw new hdErrors.InvalidEntropyArgument.NotEnoughEntropy(hexa); + } + if (hexa.length > MAXIMUM_ENTROPY_BITS * BITS_TO_BYTES) { + throw new hdErrors.InvalidEntropyArgument.TooMuchEntropy(hexa); + } + var hash = Hash.sha512hmac(hexa, new buffer.Buffer('Bitcoin seed')); + + return new HDPrivateKey({ + network: Network.get(network) || Network.defaultNetwork, + depth: 0, + parentFingerPrint: 0, + childIndex: 0, + privateKey: hash.slice(0, 32), + chainCode: hash.slice(32, 64) + }); +}; + + + +HDPrivateKey.prototype._calcHDPublicKey = function () { + if (!this._hdPublicKey) { + import('./hdpublickey').then((HDPublicKey) => { + this._hdPublicKey = new HDPublicKey(this); + }); + + } +}; + +/** + * Receives a object with buffers in all the properties and populates the + * internal structure + * + * @param {Object} arg + * @param {buffer.Buffer} arg.version + * @param {buffer.Buffer} arg.depth + * @param {buffer.Buffer} arg.parentFingerPrint + * @param {buffer.Buffer} arg.childIndex + * @param {buffer.Buffer} arg.chainCode + * @param {buffer.Buffer} arg.privateKey + * @param {buffer.Buffer} arg.checksum + * @param {string=} arg.xprivkey - if set, don't recalculate the base58 + * representation + * @return {HDPrivateKey} this + */ +HDPrivateKey.prototype._buildFromBuffers = function (arg) { + /* jshint maxcomplexity: 8 */ + /* jshint maxstatements: 20 */ + + HDPrivateKey._validateBufferArguments(arg); + + JSUtil.defineImmutable(this, { + _buffers: arg + }); + + var sequence = [ + arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode, + BufferUtil.emptyBuffer(1), arg.privateKey + ]; + var concat = buffer.Buffer.concat(sequence); + if (!arg.checksum || !arg.checksum.length) { + arg.checksum = Base58Check.checksum(concat); + } else { + if (arg.checksum.toString() !== Base58Check.checksum(concat).toString()) { + throw new errors.InvalidB58Checksum(concat); + } + } + + var network = Network.get(BufferUtil.integerFromBuffer(arg.version)); + var xprivkey; + xprivkey = Base58Check.encode(buffer.Buffer.concat(sequence)); + arg.xprivkey = Buffer.from(xprivkey); + + var privateKey = new PrivateKey(BN.fromBuffer(arg.privateKey), network); + var publicKey = privateKey.toPublicKey(); + var size = HDPrivateKey.ParentFingerPrintSize; + var fingerPrint = Hash.sha256ripemd160(publicKey.toBuffer()).slice(0, size); + + JSUtil.defineImmutable(this, { + xprivkey: xprivkey, + network: network, + depth: BufferUtil.integerFromSingleByteBuffer(arg.depth), + privateKey: privateKey, + publicKey: publicKey, + fingerPrint: fingerPrint + }); + + this._hdPublicKey = null; + + Object.defineProperty(this, 'hdPublicKey', { + configurable: false, + enumerable: true, + get: function () { + this._calcHDPublicKey(); + return this._hdPublicKey; + } + }); + Object.defineProperty(this, 'xpubkey', { + configurable: false, + enumerable: true, + get: function () { + this._calcHDPublicKey(); + return this._hdPublicKey.xpubkey; + } + }); + return this; +}; + +HDPrivateKey._validateBufferArguments = function (arg) { + var checkBuffer = function (name, size) { + var buff = arg[name]; + assert(BufferUtil.isBuffer(buff), name + ' argument is not a buffer'); + assert( + buff.length === size, + name + ' has not the expected size: found ' + buff.length + ', expected ' + size + ); + }; + checkBuffer('version', HDPrivateKey.VersionSize); + checkBuffer('depth', HDPrivateKey.DepthSize); + checkBuffer('parentFingerPrint', HDPrivateKey.ParentFingerPrintSize); + checkBuffer('childIndex', HDPrivateKey.ChildIndexSize); + checkBuffer('chainCode', HDPrivateKey.ChainCodeSize); + checkBuffer('privateKey', HDPrivateKey.PrivateKeySize); + if (arg.checksum && arg.checksum.length) { + checkBuffer('checksum', HDPrivateKey.CheckSumSize); + } +}; + +/** + * Returns the string representation of this private key (a string starting + * with "xprv..." + * + * @return string + */ +HDPrivateKey.prototype.toString = function () { + return this.xprivkey; +}; + +/** + * Returns the console representation of this extended private key. + * @return string + */ +HDPrivateKey.prototype.inspect = function () { + return ''; +}; + +/** + * Returns a plain object with a representation of this private key. + * + * Fields include:
    + *
  • network: either 'livenet' or 'testnet' + *
  • depth: a number ranging from 0 to 255 + *
  • fingerPrint: a number ranging from 0 to 2^32-1, taken from the hash of the + *
  • associated public key + *
  • parentFingerPrint: a number ranging from 0 to 2^32-1, taken from the hash + *
  • of this parent's associated public key or zero. + *
  • childIndex: the index from which this child was derived (or zero) + *
  • chainCode: an hexa string representing a number used in the derivation + *
  • privateKey: the private key associated, in hexa representation + *
  • xprivkey: the representation of this extended private key in checksum + *
  • base58 format + *
  • checksum: the base58 checksum of xprivkey + *
+ * @return {Object} + */ +HDPrivateKey.prototype.toObject = HDPrivateKey.prototype.toJSON = function toObject() { + return { + network: Network.get(BufferUtil.integerFromBuffer(this._buffers.version), 'xprivkey').name, + depth: BufferUtil.integerFromSingleByteBuffer(this._buffers.depth), + fingerPrint: BufferUtil.integerFromBuffer(this.fingerPrint), + parentFingerPrint: BufferUtil.integerFromBuffer(this._buffers.parentFingerPrint), + childIndex: BufferUtil.integerFromBuffer(this._buffers.childIndex), + chainCode: BufferUtil.bufferToHex(this._buffers.chainCode), + privateKey: this.privateKey.toBuffer().toString('hex'), + checksum: BufferUtil.integerFromBuffer(this._buffers.checksum), + xprivkey: this.xprivkey + }; +}; + +/** + * Build a HDPrivateKey from a buffer + * + * @param {Buffer} arg + * @return {HDPrivateKey} + */ +HDPrivateKey.fromBuffer = function (arg) { + return new HDPrivateKey(arg.toString()); +}; + +/** + * Returns a buffer representation of the HDPrivateKey + * + * @return {string} + */ +HDPrivateKey.prototype.toBuffer = function () { + return BufferUtil.copy(this._buffers.xprivkey); +}; + +HDPrivateKey.DefaultDepth = 0; +HDPrivateKey.DefaultFingerprint = 0; +HDPrivateKey.DefaultChildIndex = 0; +HDPrivateKey.Hardened = 0x80000000; +HDPrivateKey.MaxIndex = 2 * HDPrivateKey.Hardened; + +HDPrivateKey.RootElementAlias = ['m', 'M', 'm\'', 'M\'']; + +HDPrivateKey.VersionSize = 4; +HDPrivateKey.DepthSize = 1; +HDPrivateKey.ParentFingerPrintSize = 4; +HDPrivateKey.ChildIndexSize = 4; +HDPrivateKey.ChainCodeSize = 32; +HDPrivateKey.PrivateKeySize = 32; +HDPrivateKey.CheckSumSize = 4; + +HDPrivateKey.DataLength = 78; +HDPrivateKey.SerializedByteSize = 82; + +HDPrivateKey.VersionStart = 0; +HDPrivateKey.VersionEnd = HDPrivateKey.VersionStart + HDPrivateKey.VersionSize; +HDPrivateKey.DepthStart = HDPrivateKey.VersionEnd; +HDPrivateKey.DepthEnd = HDPrivateKey.DepthStart + HDPrivateKey.DepthSize; +HDPrivateKey.ParentFingerPrintStart = HDPrivateKey.DepthEnd; +HDPrivateKey.ParentFingerPrintEnd = HDPrivateKey.ParentFingerPrintStart + HDPrivateKey.ParentFingerPrintSize; +HDPrivateKey.ChildIndexStart = HDPrivateKey.ParentFingerPrintEnd; +HDPrivateKey.ChildIndexEnd = HDPrivateKey.ChildIndexStart + HDPrivateKey.ChildIndexSize; +HDPrivateKey.ChainCodeStart = HDPrivateKey.ChildIndexEnd; +HDPrivateKey.ChainCodeEnd = HDPrivateKey.ChainCodeStart + HDPrivateKey.ChainCodeSize; +HDPrivateKey.PrivateKeyStart = HDPrivateKey.ChainCodeEnd + 1; +HDPrivateKey.PrivateKeyEnd = HDPrivateKey.PrivateKeyStart + HDPrivateKey.PrivateKeySize; +HDPrivateKey.ChecksumStart = HDPrivateKey.PrivateKeyEnd; +HDPrivateKey.ChecksumEnd = HDPrivateKey.ChecksumStart + HDPrivateKey.CheckSumSize; + +assert(HDPrivateKey.ChecksumEnd === HDPrivateKey.SerializedByteSize); + +export default HDPrivateKey; diff --git a/src/backend/wallet/lib/hdpublickey.js b/src/backend/wallet/lib/hdpublickey.js new file mode 100644 index 0000000..bda35d5 --- /dev/null +++ b/src/backend/wallet/lib/hdpublickey.js @@ -0,0 +1,497 @@ +'use strict'; + +import _ from 'lodash'; +import $ from './util/preconditions'; + +import BN from './crypto/bn'; +import Base58 from './encoding/base58'; +import Base58Check from './encoding/base58check'; +import Hash from './crypto/hash'; +import HDPrivateKey from './hdprivatekey'; +import Network from './networks'; +import Point from './crypto/point'; +import PublicKey from './publickey'; + +import bitcoreErrors from './errors'; +var errors = bitcoreErrors; +var hdErrors = bitcoreErrors.HDPublicKey; +import assert from 'assert'; + +import JSUtil from './util/js'; + +import BufferUtil from './util/buffer'; + +/** + * The representation of an hierarchically derived public key. + * + * See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki + * + * @constructor + * @param {Object|string|Buffer} arg + */ +function HDPublicKey(arg) { + /* jshint maxcomplexity: 12 */ + /* jshint maxstatements: 20 */ + if (arg instanceof HDPublicKey) { + return arg; + } + if (!(this instanceof HDPublicKey)) { + return new HDPublicKey(arg); + } + if (arg) { + if (_.isString(arg) || BufferUtil.isBuffer(arg)) { + var error = HDPublicKey.getSerializedError(arg); + if (!error) { + return this._buildFromSerialized(arg); + } else if (BufferUtil.isBuffer(arg) && !HDPublicKey.getSerializedError(arg.toString())) { + return this._buildFromSerialized(arg.toString()); + } else { + if (error instanceof hdErrors.ArgumentIsPrivateExtended) { + return new HDPrivateKey(arg).hdPublicKey; + } + throw error; + } + } else { + if (_.isObject(arg)) { + if (arg instanceof HDPrivateKey) { + return this._buildFromPrivate(arg); + } else { + return this._buildFromObject(arg); + } + } else { + throw new hdErrors.UnrecognizedArgument(arg); + } + } + } else { + throw new hdErrors.MustSupplyArgument(); + } +} + +/** + * Verifies that a given path is valid. + * + * @param {string|number} arg + * @return {boolean} + */ +HDPublicKey.isValidPath = function (arg) { + if (_.isString(arg)) { + var indexes = HDPrivateKey._getDerivationIndexes(arg); + return indexes !== null && _.every(indexes, HDPublicKey.isValidPath); + } + + if (_.isNumber(arg)) { + return arg >= 0 && arg < HDPublicKey.Hardened; + } + + return false; +}; + +/** + * WARNING: This method is deprecated. Use deriveChild instead. + * + * + * Get a derivated child based on a string or number. + * + * If the first argument is a string, it's parsed as the full path of + * derivation. Valid values for this argument include "m" (which returns the + * same public key), "m/0/1/40/2/1000". + * + * Note that hardened keys can't be derived from a public extended key. + * + * If the first argument is a number, the child with that index will be + * derived. See the example usage for clarification. + * + * @example + * ```javascript + * var parent = new HDPublicKey('xpub...'); + * var child_0_1_2 = parent.derive(0).derive(1).derive(2); + * var copy_of_child_0_1_2 = parent.derive("m/0/1/2"); + * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2); + * ``` + * + * @param {string|number} arg + */ +HDPublicKey.prototype.derive = function (arg, hardened) { + return this.deriveChild(arg, hardened); +}; + +/** + * WARNING: This method will not be officially supported until v1.0.0. + * + * + * Get a derivated child based on a string or number. + * + * If the first argument is a string, it's parsed as the full path of + * derivation. Valid values for this argument include "m" (which returns the + * same public key), "m/0/1/40/2/1000". + * + * Note that hardened keys can't be derived from a public extended key. + * + * If the first argument is a number, the child with that index will be + * derived. See the example usage for clarification. + * + * @example + * ```javascript + * var parent = new HDPublicKey('xpub...'); + * var child_0_1_2 = parent.deriveChild(0).deriveChild(1).deriveChild(2); + * var copy_of_child_0_1_2 = parent.deriveChild("m/0/1/2"); + * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2); + * ``` + * + * @param {string|number} arg + */ +HDPublicKey.prototype.deriveChild = function (arg, hardened) { + if (_.isNumber(arg)) { + return this._deriveWithNumber(arg, hardened); + } else if (_.isString(arg)) { + return this._deriveFromString(arg); + } else { + throw new hdErrors.InvalidDerivationArgument(arg); + } +}; + +HDPublicKey.prototype._deriveWithNumber = function (index, hardened) { + if (index >= HDPublicKey.Hardened || hardened) { + throw new hdErrors.InvalidIndexCantDeriveHardened(); + } + if (index < 0) { + throw new hdErrors.InvalidPath(index); + } + + var indexBuffer = BufferUtil.integerAsBuffer(index); + var data = BufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]); + var hash = Hash.sha512hmac(data, this._buffers.chainCode); + var leftPart = BN.fromBuffer(hash.slice(0, 32), { size: 32 }); + var chainCode = hash.slice(32, 64); + + var publicKey; + try { + publicKey = PublicKey.fromPoint(Point.getG().mul(leftPart).add(this.publicKey.point)); + } catch (e) { + return this._deriveWithNumber(index + 1); + } + + var derived = new HDPublicKey({ + network: this.network, + depth: this.depth + 1, + parentFingerPrint: this.fingerPrint, + childIndex: index, + chainCode: chainCode, + publicKey: publicKey + }); + + return derived; +}; + +HDPublicKey.prototype._deriveFromString = function (path) { + /* jshint maxcomplexity: 8 */ + if (_.includes(path, "'")) { + throw new hdErrors.InvalidIndexCantDeriveHardened(); + } else if (!HDPublicKey.isValidPath(path)) { + throw new hdErrors.InvalidPath(path); + } + + var indexes = HDPrivateKey._getDerivationIndexes(path); + var derived = indexes.reduce(function (prev, index) { + return prev._deriveWithNumber(index); + }, this); + + return derived; +}; + +/** + * Verifies that a given serialized public key in base58 with checksum format + * is valid. + * + * @param {string|Buffer} data - the serialized public key + * @param {string|Network=} network - optional, if present, checks that the + * network provided matches the network serialized. + * @return {boolean} + */ +HDPublicKey.isValidSerialized = function (data, network) { + return _.isNull(HDPublicKey.getSerializedError(data, network)); +}; + +/** + * Checks what's the error that causes the validation of a serialized public key + * in base58 with checksum to fail. + * + * @param {string|Buffer} data - the serialized public key + * @param {string|Network=} network - optional, if present, checks that the + * network provided matches the network serialized. + * @return {errors|null} + */ +HDPublicKey.getSerializedError = function (data, network) { + /* jshint maxcomplexity: 10 */ + /* jshint maxstatements: 20 */ + if (!(_.isString(data) || BufferUtil.isBuffer(data))) { + return new hdErrors.UnrecognizedArgument('expected buffer or string'); + } + if (!Base58.validCharacters(data)) { + return new errors.InvalidB58Char('(unknown)', data); + } + try { + data = Base58Check.decode(data); + } catch (e) { + return new errors.InvalidB58Checksum(data); + } + if (data.length !== HDPublicKey.DataSize) { + return new hdErrors.InvalidLength(data); + } + if (!_.isUndefined(network)) { + var error = HDPublicKey._validateNetwork(data, network); + if (error) { + return error; + } + } + var version = BufferUtil.integerFromBuffer(data.slice(0, 4)); + if (version === Network.livenet.xprivkey || version === Network.testnet.xprivkey) { + return new hdErrors.ArgumentIsPrivateExtended(); + } + return null; +}; + +HDPublicKey._validateNetwork = function (data, networkArg) { + var network = Network.get(networkArg); + if (!network) { + return new errors.InvalidNetworkArgument(networkArg); + } + var version = data.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd); + if (BufferUtil.integerFromBuffer(version) !== network.xpubkey) { + return new errors.InvalidNetwork(version); + } + return null; +}; + +HDPublicKey.prototype._buildFromPrivate = function (arg) { + var args = _.clone(arg._buffers); + var point = Point.getG().mul(BN.fromBuffer(args.privateKey)); + args.publicKey = Point.pointToCompressed(point); + args.version = BufferUtil.integerAsBuffer(Network.get(BufferUtil.integerFromBuffer(args.version)).xpubkey); + args.privateKey = undefined; + args.checksum = undefined; + args.xprivkey = undefined; + return this._buildFromBuffers(args); +}; + +HDPublicKey.prototype._buildFromObject = function (arg) { + /* jshint maxcomplexity: 10 */ + // TODO: Type validation + var buffers = { + version: arg.network ? BufferUtil.integerAsBuffer(Network.get(arg.network).xpubkey) : arg.version, + depth: _.isNumber(arg.depth) ? BufferUtil.integerAsSingleByteBuffer(arg.depth) : arg.depth, + parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? BufferUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint, + childIndex: _.isNumber(arg.childIndex) ? BufferUtil.integerAsBuffer(arg.childIndex) : arg.childIndex, + chainCode: _.isString(arg.chainCode) ? BufferUtil.hexToBuffer(arg.chainCode) : arg.chainCode, + publicKey: _.isString(arg.publicKey) ? BufferUtil.hexToBuffer(arg.publicKey) : + BufferUtil.isBuffer(arg.publicKey) ? arg.publicKey : arg.publicKey.toBuffer(), + checksum: _.isNumber(arg.checksum) ? BufferUtil.integerAsBuffer(arg.checksum) : arg.checksum + }; + return this._buildFromBuffers(buffers); +}; + +HDPublicKey.prototype._buildFromSerialized = function (arg) { + var decoded = Base58Check.decode(arg); + var buffers = { + version: decoded.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd), + depth: decoded.slice(HDPublicKey.DepthStart, HDPublicKey.DepthEnd), + parentFingerPrint: decoded.slice(HDPublicKey.ParentFingerPrintStart, + HDPublicKey.ParentFingerPrintEnd), + childIndex: decoded.slice(HDPublicKey.ChildIndexStart, HDPublicKey.ChildIndexEnd), + chainCode: decoded.slice(HDPublicKey.ChainCodeStart, HDPublicKey.ChainCodeEnd), + publicKey: decoded.slice(HDPublicKey.PublicKeyStart, HDPublicKey.PublicKeyEnd), + checksum: decoded.slice(HDPublicKey.ChecksumStart, HDPublicKey.ChecksumEnd), + xpubkey: arg + }; + return this._buildFromBuffers(buffers); +}; + +/** + * Receives a object with buffers in all the properties and populates the + * internal structure + * + * @param {Object} arg + * @param {buffer.Buffer} arg.version + * @param {buffer.Buffer} arg.depth + * @param {buffer.Buffer} arg.parentFingerPrint + * @param {buffer.Buffer} arg.childIndex + * @param {buffer.Buffer} arg.chainCode + * @param {buffer.Buffer} arg.publicKey + * @param {buffer.Buffer} arg.checksum + * @param {string=} arg.xpubkey - if set, don't recalculate the base58 + * representation + * @return {HDPublicKey} this + */ +HDPublicKey.prototype._buildFromBuffers = function (arg) { + /* jshint maxcomplexity: 8 */ + /* jshint maxstatements: 20 */ + + HDPublicKey._validateBufferArguments(arg); + + JSUtil.defineImmutable(this, { + _buffers: arg + }); + + var sequence = [ + arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode, + arg.publicKey + ]; + var concat = BufferUtil.concat(sequence); + var checksum = Base58Check.checksum(concat); + if (!arg.checksum || !arg.checksum.length) { + arg.checksum = checksum; + } else { + if (arg.checksum.toString('hex') !== checksum.toString('hex')) { + throw new errors.InvalidB58Checksum(concat, checksum); + } + } + var network = Network.get(BufferUtil.integerFromBuffer(arg.version)); + + var xpubkey; + xpubkey = Base58Check.encode(BufferUtil.concat(sequence)); + arg.xpubkey = Buffer.from(xpubkey); + + var publicKey = new PublicKey(arg.publicKey, { network: network }); + var size = HDPublicKey.ParentFingerPrintSize; + var fingerPrint = Hash.sha256ripemd160(publicKey.toBuffer()).slice(0, size); + + JSUtil.defineImmutable(this, { + xpubkey: xpubkey, + network: network, + depth: BufferUtil.integerFromSingleByteBuffer(arg.depth), + publicKey: publicKey, + fingerPrint: fingerPrint + }); + + return this; +}; + +HDPublicKey._validateBufferArguments = function (arg) { + var checkBuffer = function (name, size) { + var buff = arg[name]; + assert(BufferUtil.isBuffer(buff), name + ' argument is not a buffer, it\'s ' + typeof buff); + assert( + buff.length === size, + name + ' has not the expected size: found ' + buff.length + ', expected ' + size + ); + }; + checkBuffer('version', HDPublicKey.VersionSize); + checkBuffer('depth', HDPublicKey.DepthSize); + checkBuffer('parentFingerPrint', HDPublicKey.ParentFingerPrintSize); + checkBuffer('childIndex', HDPublicKey.ChildIndexSize); + checkBuffer('chainCode', HDPublicKey.ChainCodeSize); + checkBuffer('publicKey', HDPublicKey.PublicKeySize); + if (arg.checksum && arg.checksum.length) { + checkBuffer('checksum', HDPublicKey.CheckSumSize); + } +}; + +HDPublicKey.fromString = function (arg) { + $.checkArgument(_.isString(arg), 'No valid string was provided'); + return new HDPublicKey(arg); +}; + +HDPublicKey.fromObject = function (arg) { + $.checkArgument(_.isObject(arg), 'No valid argument was provided'); + return new HDPublicKey(arg); +}; + +/** + * Returns the base58 checked representation of the public key + * @return {string} a string starting with "xpub..." in livenet + */ +HDPublicKey.prototype.toString = function () { + return this.xpubkey; +}; + +/** + * Returns the console representation of this extended public key. + * @return string + */ +HDPublicKey.prototype.inspect = function () { + return ''; +}; + +/** + * Returns a plain JavaScript object with information to reconstruct a key. + * + * Fields are:
    + *
  • network: 'livenet' or 'testnet' + *
  • depth: a number from 0 to 255, the depth to the master extended key + *
  • fingerPrint: a number of 32 bits taken from the hash of the public key + *
  • fingerPrint: a number of 32 bits taken from the hash of this key's + *
  • parent's public key + *
  • childIndex: index with which this key was derived + *
  • chainCode: string in hexa encoding used for derivation + *
  • publicKey: string, hexa encoded, in compressed key format + *
  • checksum: BufferUtil.integerFromBuffer(this._buffers.checksum), + *
  • xpubkey: the string with the base58 representation of this extended key + *
  • checksum: the base58 checksum of xpubkey + *
+ */ +HDPublicKey.prototype.toObject = HDPublicKey.prototype.toJSON = function toObject() { + return { + network: Network.get(BufferUtil.integerFromBuffer(this._buffers.version)).name, + depth: BufferUtil.integerFromSingleByteBuffer(this._buffers.depth), + fingerPrint: BufferUtil.integerFromBuffer(this.fingerPrint), + parentFingerPrint: BufferUtil.integerFromBuffer(this._buffers.parentFingerPrint), + childIndex: BufferUtil.integerFromBuffer(this._buffers.childIndex), + chainCode: BufferUtil.bufferToHex(this._buffers.chainCode), + publicKey: this.publicKey.toString(), + checksum: BufferUtil.integerFromBuffer(this._buffers.checksum), + xpubkey: this.xpubkey + }; +}; + +/** + * Create a HDPublicKey from a buffer argument + * + * @param {Buffer} arg + * @return {HDPublicKey} + */ +HDPublicKey.fromBuffer = function (arg) { + return new HDPublicKey(arg); +}; + +/** + * Return a buffer representation of the xpubkey + * + * @return {Buffer} + */ +HDPublicKey.prototype.toBuffer = function () { + return BufferUtil.copy(this._buffers.xpubkey); +}; + +HDPublicKey.Hardened = 0x80000000; +HDPublicKey.RootElementAlias = ['m', 'M']; + +HDPublicKey.VersionSize = 4; +HDPublicKey.DepthSize = 1; +HDPublicKey.ParentFingerPrintSize = 4; +HDPublicKey.ChildIndexSize = 4; +HDPublicKey.ChainCodeSize = 32; +HDPublicKey.PublicKeySize = 33; +HDPublicKey.CheckSumSize = 4; + +HDPublicKey.DataSize = 78; +HDPublicKey.SerializedByteSize = 82; + +HDPublicKey.VersionStart = 0; +HDPublicKey.VersionEnd = HDPublicKey.VersionStart + HDPublicKey.VersionSize; +HDPublicKey.DepthStart = HDPublicKey.VersionEnd; +HDPublicKey.DepthEnd = HDPublicKey.DepthStart + HDPublicKey.DepthSize; +HDPublicKey.ParentFingerPrintStart = HDPublicKey.DepthEnd; +HDPublicKey.ParentFingerPrintEnd = HDPublicKey.ParentFingerPrintStart + HDPublicKey.ParentFingerPrintSize; +HDPublicKey.ChildIndexStart = HDPublicKey.ParentFingerPrintEnd; +HDPublicKey.ChildIndexEnd = HDPublicKey.ChildIndexStart + HDPublicKey.ChildIndexSize; +HDPublicKey.ChainCodeStart = HDPublicKey.ChildIndexEnd; +HDPublicKey.ChainCodeEnd = HDPublicKey.ChainCodeStart + HDPublicKey.ChainCodeSize; +HDPublicKey.PublicKeyStart = HDPublicKey.ChainCodeEnd; +HDPublicKey.PublicKeyEnd = HDPublicKey.PublicKeyStart + HDPublicKey.PublicKeySize; +HDPublicKey.ChecksumStart = HDPublicKey.PublicKeyEnd; +HDPublicKey.ChecksumEnd = HDPublicKey.ChecksumStart + HDPublicKey.CheckSumSize; + +assert(HDPublicKey.PublicKeyEnd === HDPublicKey.DataSize); +assert(HDPublicKey.ChecksumEnd === HDPublicKey.SerializedByteSize); + +export default HDPublicKey; diff --git a/src/backend/wallet/lib/mnemonic.js b/src/backend/wallet/lib/mnemonic.js new file mode 100644 index 0000000..9a46a0c --- /dev/null +++ b/src/backend/wallet/lib/mnemonic.js @@ -0,0 +1,303 @@ +'use strict'; + +import bitcore from '..'; + +import unorm from 'unorm'; + + +import pbkdf2 from './pbkdf2'; +import errors from './errors'; + +import words from './words'; + +var _ = bitcore.deps._; + +var BN = bitcore.crypto.BN; + +var Hash = bitcore.crypto.Hash; +var Random = bitcore.crypto.Random; + +var $ = bitcore.util.preconditions; + + +/** + * This is an immutable class that represents a BIP39 Mnemonic code. + * See BIP39 specification for more info: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki + * A Mnemonic code is a a group of easy to remember words used for the generation + * of deterministic wallets. A Mnemonic can be used to generate a seed using + * an optional passphrase, for later generate a HDPrivateKey. + * + * @example + * // generate a random mnemonic + * var mnemonic = new Mnemonic(); + * var phrase = mnemonic.phrase; + * + * // use a different language + * var mnemonic = new Mnemonic(Mnemonic.Words.SPANISH); + * var xprivkey = mnemonic.toHDPrivateKey(); + * + * @param {*=} data - a seed, phrase, or entropy to initialize (can be skipped) + * @param {Array=} wordlist - the wordlist to generate mnemonics from + * @returns {Mnemonic} A new instance of Mnemonic + * @constructor + */ +var Mnemonic = function (data, wordlist) { + if (!(this instanceof Mnemonic)) { + return new Mnemonic(data, wordlist); + } + + if (_.isArray(data)) { + wordlist = data; + data = null; + } + + + // handle data overloading + var ent, phrase, seed; + if (Buffer.isBuffer(data)) { + seed = data; + } else if (_.isString(data)) { + phrase = unorm.nfkd(data); + } else if (_.isNumber(data)) { + ent = data; + } else if (data) { + throw new bitcore.errors.InvalidArgument('data', 'Must be a Buffer, a string or an integer'); + } + ent = ent || 128; + + + // check and detect wordlist + wordlist = wordlist || Mnemonic._getDictionary(phrase); + if (phrase && !wordlist) { + throw new errors.UnknownWordlist(phrase); + } + wordlist = wordlist || Mnemonic.Words.ENGLISH; + + if (seed) { + phrase = Mnemonic._entropy2mnemonic(seed, wordlist); + } + + + // validate phrase and ent + if (phrase && !Mnemonic.isValid(phrase, wordlist)) { + throw new errors.InvalidMnemonic(phrase); + } + if (ent % 32 !== 0 || ent < 128) { + throw new bitcore.errors.InvalidArgument('ENT', 'Values must be ENT > 128 and ENT % 32 == 0'); + } + + phrase = phrase || Mnemonic._mnemonic(ent, wordlist); + + Object.defineProperty(this, 'wordlist', { + configurable: false, + value: wordlist + }); + + Object.defineProperty(this, 'phrase', { + configurable: false, + value: phrase + }); +}; + +Mnemonic.Words = words; + +/** + * Will return a boolean if the mnemonic is valid + * + * @example + * + * var valid = Mnemonic.isValid('lab rescue lunch elbow recall phrase perfect donkey biology guess moment husband'); + * // true + * + * @param {String} mnemonic - The mnemonic string + * @param {String} [wordlist] - The wordlist used + * @returns {boolean} + */ +Mnemonic.isValid = function (mnemonic, wordlist) { + mnemonic = unorm.nfkd(mnemonic); + wordlist = wordlist || Mnemonic._getDictionary(mnemonic); + + if (!wordlist) { + return false; + } + + var words = mnemonic.split(' '); + var bin = ''; + for (var i = 0; i < words.length; i++) { + var ind = wordlist.indexOf(words[i]); + if (ind < 0) return false; + bin = bin + ('00000000000' + ind.toString(2)).slice(-11); + } + + var cs = bin.length / 33; + var hash_bits = bin.slice(-cs); + var nonhash_bits = bin.slice(0, bin.length - cs); + var buf = new Buffer(nonhash_bits.length / 8); + for (i = 0; i < nonhash_bits.length / 8; i++) { + buf.writeUInt8(parseInt(bin.slice(i * 8, (i + 1) * 8), 2), i); + } + var expected_hash_bits = Mnemonic._entropyChecksum(buf); + return expected_hash_bits === hash_bits; +}; + +/** + * Internal function to check if a mnemonic belongs to a wordlist. + * + * @param {String} mnemonic - The mnemonic string + * @param {String} wordlist - The wordlist + * @returns {boolean} + */ +Mnemonic._belongsToWordlist = function (mnemonic, wordlist) { + var words = unorm.nfkd(mnemonic).split(' '); + for (var i = 0; i < words.length; i++) { + var ind = wordlist.indexOf(words[i]); + if (ind < 0) return false; + } + return true; +}; + +/** + * Internal function to detect the wordlist used to generate the mnemonic. + * + * @param {String} mnemonic - The mnemonic string + * @returns {Array} the wordlist or null + */ +Mnemonic._getDictionary = function (mnemonic) { + if (!mnemonic) return null; + + var dicts = Object.keys(Mnemonic.Words); + for (var i = 0; i < dicts.length; i++) { + var key = dicts[i]; + if (Mnemonic._belongsToWordlist(mnemonic, Mnemonic.Words[key])) { + return Mnemonic.Words[key]; + } + } + return null; +}; + +/** + * Will generate a seed based on the mnemonic and optional passphrase. + * + * @param {String} [passphrase] + * @returns {Buffer} + */ +Mnemonic.prototype.toSeed = function (passphrase) { + passphrase = passphrase || ''; + return pbkdf2(unorm.nfkd(this.phrase), unorm.nfkd('mnemonic' + passphrase), 2048, 64); +}; + +/** + * Will generate a Mnemonic object based on a seed. + * + * @param {Buffer} [seed] + * @param {string} [wordlist] + * @returns {Mnemonic} + */ +Mnemonic.fromSeed = function (seed, wordlist) { + $.checkArgument(Buffer.isBuffer(seed), 'seed must be a Buffer.'); + $.checkArgument(_.isArray(wordlist) || _.isString(wordlist), 'wordlist must be a string or an array.'); + return new Mnemonic(seed, wordlist); +}; + +/** + * + * Generates a HD Private Key from a Mnemonic. + * Optionally receive a passphrase and bitcoin network. + * + * @param {String=} [passphrase] + * @param {Network|String|number=} [network] - The network: 'livenet' or 'testnet' + * @returns {HDPrivateKey} + */ +Mnemonic.prototype.toHDPrivateKey = function (passphrase, network) { + var seed = this.toSeed(passphrase); + return bitcore.HDPrivateKey.fromSeed(seed, network); +}; + +/** + * Will return a the string representation of the mnemonic + * + * @returns {String} Mnemonic + */ +Mnemonic.prototype.toString = function () { + return this.phrase; +}; + +/** + * Will return a string formatted for the console + * + * @returns {String} Mnemonic + */ +Mnemonic.prototype.inspect = function () { + return ''; +}; + +/** + * Internal function to generate a random mnemonic + * + * @param {Number} ENT - Entropy size, defaults to 128 + * @param {Array} wordlist - Array of words to generate the mnemonic + * @returns {String} Mnemonic string + */ +Mnemonic._mnemonic = function (ENT, wordlist) { + var buf = Random.getRandomBuffer(ENT / 8); + return Mnemonic._entropy2mnemonic(buf, wordlist); +}; + +/** + * Internal function to generate mnemonic based on entropy + * + * @param {Number} entropy - Entropy buffer + * @param {Array} wordlist - Array of words to generate the mnemonic + * @returns {String} Mnemonic string + */ +Mnemonic._entropy2mnemonic = function (entropy, wordlist) { + var bin = ''; + for (var i = 0; i < entropy.length; i++) { + bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8); + } + + bin = bin + Mnemonic._entropyChecksum(entropy); + if (bin.length % 11 !== 0) { + throw new errors.InvalidEntropy(bin); + } + var mnemonic = []; + for (i = 0; i < bin.length / 11; i++) { + var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); + mnemonic.push(wordlist[wi]); + } + var ret; + if (wordlist === Mnemonic.Words.JAPANESE) { + ret = mnemonic.join('\u3000'); + } else { + ret = mnemonic.join(' '); + } + return ret; +}; + +/** + * Internal function to create checksum of entropy + * + * @param entropy + * @returns {string} Checksum of entropy length / 32 + * @private + */ +Mnemonic._entropyChecksum = function (entropy) { + var hash = Hash.sha256(entropy); + var bits = entropy.length * 8; + var cs = bits / 32; + + var hashbits = new BN(hash.toString('hex'), 16).toString(2); + + // zero pad the hash bits + while (hashbits.length % 256 !== 0) { + hashbits = '0' + hashbits; + } + + var checksum = hashbits.slice(0, cs); + + return checksum; +}; + +Mnemonic.bitcore = bitcore; + +export default Mnemonic; diff --git a/src/backend/wallet/lib/networks.js b/src/backend/wallet/lib/networks.js new file mode 100644 index 0000000..0db4c6d --- /dev/null +++ b/src/backend/wallet/lib/networks.js @@ -0,0 +1,269 @@ +'use strict'; +import _ from 'lodash'; + +import BufferUtil from './util/buffer'; +import JSUtil from './util/js'; +var networks = []; +var networkMaps = {}; + +/** + * A network is merely a map containing values that correspond to version + * numbers for each bitcoin network. Currently only supporting "livenet" + * (a.k.a. "mainnet") and "testnet". + * @constructor + */ +function Network() { } + +Network.prototype.toString = function toString() { + return this.name; +}; + +/** + * @function + * @member Networks#get + * Retrieves the network associated with a magic number or string. + * @param {string|number|Network} arg + * @param {string|Array} keys - if set, only check if the magic number associated with this name matches + * @return Network + */ +function get(arg, keys) { + if (~networks.indexOf(arg)) { + return arg; + } + if (keys) { + if (!_.isArray(keys)) { + keys = [keys]; + } + var containsArg = function (key) { + return networks[index][key] === arg; + }; + for (var index in networks) { + if (_.some(keys, containsArg)) { + return networks[index]; + } + } + return undefined; + } + return networkMaps[arg]; +} + +/** + * @function + * @member Networks#add + * Will add a custom Network + * @param {Object} data + * @param {string} data.name - The name of the network + * @param {string} data.alias - The aliased name of the network + * @param {Number} data.pubkeyhash - The publickey hash prefix + * @param {Number} data.privatekey - The privatekey prefix + * @param {Number} data.scripthash - The scripthash prefix + * @param {Number} data.xpubkey - The extended public key magic + * @param {Number} data.xprivkey - The extended private key magic + * @param {Number} data.networkMagic - The network magic number + * @param {Number} data.port - The network port + * @param {Array} data.dnsSeeds - An array of dns seeds + * @return Network + */ +function addNetwork(data) { + + var network = new Network(); + + JSUtil.defineImmutable(network, { + name: data.name, + alias: data.alias, + pubkeyhash: data.pubkeyhash, + privatekey: data.privatekey, + scripthash: data.scripthash, + xpubkey: data.xpubkey, + xprivkey: data.xprivkey + }); + + if (data.networkMagic) { + JSUtil.defineImmutable(network, { + networkMagic: BufferUtil.integerAsBuffer(data.networkMagic) + }); + } + + if (data.port) { + JSUtil.defineImmutable(network, { + port: data.port + }); + } + + if (data.dnsSeeds) { + JSUtil.defineImmutable(network, { + dnsSeeds: data.dnsSeeds + }); + } + _.each(network, function (value) { + if (!_.isUndefined(value) && !_.isObject(value)) { + networkMaps[value] = network; + } + }); + + networks.push(network); + + return network; + +} + +/** + * @function + * @member Networks#remove + * Will remove a custom network + * @param {Network} network + */ +function removeNetwork(network) { + for (var i = 0; i < networks.length; i++) { + if (networks[i] === network) { + networks.splice(i, 1); + } + } + for (var key in networkMaps) { + if (networkMaps[key] === network) { + delete networkMaps[key]; + } + } +} + +addNetwork({ + name: 'livenet', + alias: 'mainnet', + pubkeyhash: 73, + privatekey: 153, + scripthash: 51, + xpubkey: 0x4c1d3d5f, + xprivkey: 0x4c233f4b, + networkMagic: 0xf9beb4d9, + port: 8333, + dnsSeeds: [ + 'seed.bitcoin.sipa.be', + 'dnsseed.bluematt.me', + 'dnsseed.bitcoin.dashjr.org', + 'seed.bitcoinstats.com', + 'seed.bitnodes.io', + 'bitseed.xf2.org' + ] +}); + +/** + * @instance + * @member Networks#livenet + */ +var livenet = get('livenet'); + +addNetwork({ + name: 'testnet', + alias: 'regtest', + pubkeyhash: 135, + privatekey: 210, + scripthash: 88, + xpubkey: 0x7d573a2c, + xprivkey: 0x7d5c5a26 +}); + +/** + * @instance + * @member Networks#testnet + */ +var testnet = get('testnet'); + +// Add configurable values for testnet/regtest + +var TESTNET = { + PORT: 18333, + NETWORK_MAGIC: BufferUtil.integerAsBuffer(0x0b110907), + DNS_SEEDS: [ + 'testnet-seed.bitcoin.petertodd.org', + 'testnet-seed.bluematt.me', + 'testnet-seed.alexykot.me', + 'testnet-seed.bitcoin.schildbach.de' + ] +}; + +for (var key in TESTNET) { + if (!_.isObject(TESTNET[key])) { + networkMaps[TESTNET[key]] = testnet; + } +} + +var REGTEST = { + PORT: 18444, + NETWORK_MAGIC: BufferUtil.integerAsBuffer(0xfabfb5da), + DNS_SEEDS: [] +}; + +for (var key in REGTEST) { + if (!_.isObject(REGTEST[key])) { + networkMaps[REGTEST[key]] = testnet; + } +} + +Object.defineProperty(testnet, 'port', { + enumerable: true, + configurable: false, + get: function () { + if (this.regtestEnabled) { + return REGTEST.PORT; + } else { + return TESTNET.PORT; + } + } +}); + +Object.defineProperty(testnet, 'networkMagic', { + enumerable: true, + configurable: false, + get: function () { + if (this.regtestEnabled) { + return REGTEST.NETWORK_MAGIC; + } else { + return TESTNET.NETWORK_MAGIC; + } + } +}); + +Object.defineProperty(testnet, 'dnsSeeds', { + enumerable: true, + configurable: false, + get: function () { + if (this.regtestEnabled) { + return REGTEST.DNS_SEEDS; + } else { + return TESTNET.DNS_SEEDS; + } + } +}); + +/** + * @function + * @member Networks#enableRegtest + * Will enable regtest features for testnet + */ +function enableRegtest() { + testnet.regtestEnabled = true; +} + +/** + * @function + * @member Networks#disableRegtest + * Will disable regtest features for testnet + */ +function disableRegtest() { + testnet.regtestEnabled = false; +} + +/** + * @namespace Networks + */ +export default { + add: addNetwork, + remove: removeNetwork, + defaultNetwork: livenet, + livenet: livenet, + mainnet: livenet, + testnet: testnet, + get: get, + enableRegtest: enableRegtest, + disableRegtest: disableRegtest +}; diff --git a/src/backend/wallet/lib/opcode.js b/src/backend/wallet/lib/opcode.js new file mode 100644 index 0000000..74d7fd6 --- /dev/null +++ b/src/backend/wallet/lib/opcode.js @@ -0,0 +1,248 @@ +'use strict'; + +import _ from 'lodash'; +import $ from './util/preconditions'; +import BufferUtil from './util/buffer'; +import JSUtil from './util/js'; + +function Opcode(num) { + if (!(this instanceof Opcode)) { + return new Opcode(num); + } + + var value; + + if (_.isNumber(num)) { + value = num; + } else if (_.isString(num)) { + value = Opcode.map[num]; + } else { + throw new TypeError('Unrecognized num type: "' + typeof (num) + '" for Opcode'); + } + + JSUtil.defineImmutable(this, { + num: value + }); + + return this; +} + +Opcode.fromBuffer = function (buf) { + $.checkArgument(BufferUtil.isBuffer(buf)); + return new Opcode(Number('0x' + buf.toString('hex'))); +}; + +Opcode.fromNumber = function (num) { + $.checkArgument(_.isNumber(num)); + return new Opcode(num); +}; + +Opcode.fromString = function (str) { + $.checkArgument(_.isString(str)); + var value = Opcode.map[str]; + if (typeof value === 'undefined') { + throw new TypeError('Invalid opcodestr'); + } + return new Opcode(value); +}; + +Opcode.prototype.toHex = function () { + return this.num.toString(16); +}; + +Opcode.prototype.toBuffer = function () { + return Buffer.from(this.toHex(), 'hex'); +}; + +Opcode.prototype.toNumber = function () { + return this.num; +}; + +Opcode.prototype.toString = function () { + var str = Opcode.reverseMap[this.num]; + if (typeof str === 'undefined') { + throw new Error('Opcode does not have a string representation'); + } + return str; +}; + +Opcode.smallInt = function (n) { + $.checkArgument(_.isNumber(n), 'Invalid Argument: n should be number'); + $.checkArgument(n >= 0 && n <= 16, 'Invalid Argument: n must be between 0 and 16'); + if (n === 0) { + return Opcode('OP_0'); + } + return new Opcode(Opcode.map.OP_1 + n - 1); +}; + +Opcode.map = { + // push value + OP_FALSE: 0, + OP_0: 0, + OP_PUSHDATA1: 76, + OP_PUSHDATA2: 77, + OP_PUSHDATA4: 78, + OP_1NEGATE: 79, + OP_RESERVED: 80, + OP_TRUE: 81, + OP_1: 81, + OP_2: 82, + OP_3: 83, + OP_4: 84, + OP_5: 85, + OP_6: 86, + OP_7: 87, + OP_8: 88, + OP_9: 89, + OP_10: 90, + OP_11: 91, + OP_12: 92, + OP_13: 93, + OP_14: 94, + OP_15: 95, + OP_16: 96, + + // control + OP_NOP: 97, + OP_VER: 98, + OP_IF: 99, + OP_NOTIF: 100, + OP_VERIF: 101, + OP_VERNOTIF: 102, + OP_ELSE: 103, + OP_ENDIF: 104, + OP_VERIFY: 105, + OP_RETURN: 106, + + // stack ops + OP_TOALTSTACK: 107, + OP_FROMALTSTACK: 108, + OP_2DROP: 109, + OP_2DUP: 110, + OP_3DUP: 111, + OP_2OVER: 112, + OP_2ROT: 113, + OP_2SWAP: 114, + OP_IFDUP: 115, + OP_DEPTH: 116, + OP_DROP: 117, + OP_DUP: 118, + OP_NIP: 119, + OP_OVER: 120, + OP_PICK: 121, + OP_ROLL: 122, + OP_ROT: 123, + OP_SWAP: 124, + OP_TUCK: 125, + + // splice ops + OP_CAT: 126, + OP_SUBSTR: 127, + OP_LEFT: 128, + OP_RIGHT: 129, + OP_SIZE: 130, + + // bit logic + OP_INVERT: 131, + OP_AND: 132, + OP_OR: 133, + OP_XOR: 134, + OP_EQUAL: 135, + OP_EQUALVERIFY: 136, + OP_RESERVED1: 137, + OP_RESERVED2: 138, + + // numeric + OP_1ADD: 139, + OP_1SUB: 140, + OP_2MUL: 141, + OP_2DIV: 142, + OP_NEGATE: 143, + OP_ABS: 144, + OP_NOT: 145, + OP_0NOTEQUAL: 146, + + OP_ADD: 147, + OP_SUB: 148, + OP_MUL: 149, + OP_DIV: 150, + OP_MOD: 151, + OP_LSHIFT: 152, + OP_RSHIFT: 153, + + OP_BOOLAND: 154, + OP_BOOLOR: 155, + OP_NUMEQUAL: 156, + OP_NUMEQUALVERIFY: 157, + OP_NUMNOTEQUAL: 158, + OP_LESSTHAN: 159, + OP_GREATERTHAN: 160, + OP_LESSTHANOREQUAL: 161, + OP_GREATERTHANOREQUAL: 162, + OP_MIN: 163, + OP_MAX: 164, + + OP_WITHIN: 165, + + // crypto + OP_RIPEMD160: 166, + OP_SHA1: 167, + OP_SHA256: 168, + OP_HASH160: 169, + OP_HASH256: 170, + OP_CODESEPARATOR: 171, + OP_CHECKSIG: 172, + OP_CHECKSIGVERIFY: 173, + OP_CHECKMULTISIG: 174, + OP_CHECKMULTISIGVERIFY: 175, + + OP_CHECKLOCKTIMEVERIFY: 177, + + // expansion + OP_NOP1: 176, + OP_NOP2: 177, + OP_NOP3: 178, + OP_NOP4: 179, + OP_NOP5: 180, + OP_NOP6: 181, + OP_NOP7: 182, + OP_NOP8: 183, + OP_NOP9: 184, + OP_NOP10: 185, + + // template matching params + OP_PUBKEYHASH: 253, + OP_PUBKEY: 254, + OP_INVALIDOPCODE: 255 +}; + +Opcode.reverseMap = []; + +for (var k in Opcode.map) { + Opcode.reverseMap[Opcode.map[k]] = k; +} + +// Easier access to opcodes +_.extend(Opcode, Opcode.map); + +/** + * @returns true if opcode is one of OP_0, OP_1, ..., OP_16 + */ +Opcode.isSmallIntOp = function (opcode) { + if (opcode instanceof Opcode) { + opcode = opcode.toNumber(); + } + return ((opcode === Opcode.map.OP_0) || + ((opcode >= Opcode.map.OP_1) && (opcode <= Opcode.map.OP_16))); +}; + +/** + * Will return a string formatted for the console + * + * @returns {string} Script opcode + */ +Opcode.prototype.inspect = function () { + return ''; +}; + +export default Opcode; diff --git a/src/backend/wallet/lib/pbkdf2.js b/src/backend/wallet/lib/pbkdf2.js new file mode 100644 index 0000000..709fb2b --- /dev/null +++ b/src/backend/wallet/lib/pbkdf2.js @@ -0,0 +1,71 @@ +'use strict'; + +var crypto = require("crypto"); + +/** + * PDKBF2 + * Credit to: https://github.com/stayradiated/pbkdf2-sha512 + * Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved + */ +function pbkdf2(key, salt, iterations, dkLen) { + /* jshint maxstatements: 31 */ + /* jshint maxcomplexity: 9 */ + + var hLen = 64; //SHA512 Mac length + if (dkLen > (Math.pow(2, 32) - 1) * hLen) { + throw Error('Requested key length too long'); + } + + if (typeof key !== 'string' && !Buffer.isBuffer(key)) { + throw new TypeError('key must a string or Buffer'); + } + + if (typeof salt !== 'string' && !Buffer.isBuffer(salt)) { + throw new TypeError('salt must a string or Buffer'); + } + + if (typeof key === 'string') { + key = new Buffer(key); + } + + if (typeof salt === 'string') { + salt = new Buffer(salt); + } + + var DK = new Buffer(dkLen); + + var U = new Buffer(hLen); + var T = new Buffer(hLen); + var block1 = new Buffer(salt.length + 4); + + var l = Math.ceil(dkLen / hLen); + var r = dkLen - (l - 1) * hLen; + + salt.copy(block1, 0, 0, salt.length); + for (var i = 1; i <= l; i++) { + block1[salt.length + 0] = (i >> 24 & 0xff); + block1[salt.length + 1] = (i >> 16 & 0xff); + block1[salt.length + 2] = (i >> 8 & 0xff); + block1[salt.length + 3] = (i >> 0 & 0xff); + + U = crypto.createHmac('sha512', key).update(block1).digest(); + + U.copy(T, 0, 0, hLen); + + for (var j = 1; j < iterations; j++) { + U = crypto.createHmac('sha512', key).update(U).digest(); + + for (var k = 0; k < hLen; k++) { + T[k] ^= U[k]; + } + } + + var destPos = (i - 1) * hLen; + var len = (i === l ? r : hLen); + T.copy(DK, destPos, 0, len); + } + + return DK; +} + +export default pbkdf2; diff --git a/src/backend/wallet/lib/privatekey.js b/src/backend/wallet/lib/privatekey.js new file mode 100644 index 0000000..eb555a2 --- /dev/null +++ b/src/backend/wallet/lib/privatekey.js @@ -0,0 +1,400 @@ +'use strict'; + +import _ from 'lodash'; +import Address from './address'; +import Base58Check from './encoding/base58check'; +import BN from './crypto/bn'; +import JSUtil from './util/js'; +import Networks from './networks'; +import Point from './crypto/point'; +import PublicKey from './publickey'; +import Random from './crypto/random'; +import $ from './util/preconditions'; + +/** + * Instantiate a PrivateKey from a BN, Buffer and WIF. + * + * @example + * ```javascript + * // generate a new random key + * var key = PrivateKey(); + * + * // get the associated address + * var address = key.toAddress(); + * + * // encode into wallet export format + * var exported = key.toWIF(); + * + * // instantiate from the exported (and saved) private key + * var imported = PrivateKey.fromWIF(exported); + * ``` + * + * @param {string} data - The encoded data in various formats + * @param {Network|string=} network - a {@link Network} object, or a string with the network name + * @returns {PrivateKey} A new valid instance of an PrivateKey + * @constructor + */ +function PrivateKey(data, network) { + /* jshint maxstatements: 20 */ + /* jshint maxcomplexity: 8 */ + + if (!(this instanceof PrivateKey)) { + return new PrivateKey(data, network); + } + if (data instanceof PrivateKey) { + return data; + } + + var info = this._classifyArguments(data, network); + + // validation + if (!info.bn || info.bn.cmp(new BN(0)) === 0) { + throw new TypeError('Number can not be equal to zero, undefined, null or false'); + } + if (!info.bn.lt(Point.getN())) { + throw new TypeError('Number must be less than N'); + } + if (typeof (info.network) === 'undefined') { + throw new TypeError('Must specify the network ("livenet" or "testnet")'); + } + + JSUtil.defineImmutable(this, { + bn: info.bn, + compressed: info.compressed, + network: info.network + }); + + Object.defineProperty(this, 'publicKey', { + configurable: false, + enumerable: true, + get: this.toPublicKey.bind(this) + }); + + return this; + +}; + +/** + * Internal helper to instantiate PrivateKey internal `info` object from + * different kinds of arguments passed to the constructor. + * + * @param {*} data + * @param {Network|string=} network - a {@link Network} object, or a string with the network name + * @return {Object} + */ +PrivateKey.prototype._classifyArguments = function (data, network) { + /* jshint maxcomplexity: 10 */ + var info = { + compressed: true, + network: network ? Networks.get(network) : Networks.defaultNetwork + }; + + // detect type of data + if (_.isUndefined(data) || _.isNull(data)) { + info.bn = PrivateKey._getRandomBN(); + } else if (data instanceof BN) { + info.bn = data; + } else if (data instanceof Buffer || data instanceof Uint8Array) { + info = PrivateKey._transformBuffer(data, network); + } else if (data.bn && data.network) { + info = PrivateKey._transformObject(data); + } else if (!network && Networks.get(data)) { + info.bn = PrivateKey._getRandomBN(); + info.network = Networks.get(data); + } else if (typeof (data) === 'string') { + if (JSUtil.isHexa(data)) { + info.bn = new BN(Buffer.from(data, 'hex')); + } else { + info = PrivateKey._transformWIF(data, network); + } + } else { + throw new TypeError('First argument is an unrecognized data type.'); + } + return info; +}; + +/** + * Internal function to get a random Big Number (BN) + * + * @returns {BN} A new randomly generated BN + * @private + */ +PrivateKey._getRandomBN = function () { + var condition; + var bn; + do { + var privbuf = Random.getRandomBuffer(32); + bn = BN.fromBuffer(privbuf); + condition = bn.lt(Point.getN()); + } while (!condition); + return bn; +}; + +/** + * Internal function to transform a WIF Buffer into a private key + * + * @param {Buffer} buf - An WIF string + * @param {Network|string=} network - a {@link Network} object, or a string with the network name + * @returns {Object} An object with keys: bn, network and compressed + * @private + */ +PrivateKey._transformBuffer = function (buf, network) { + + var info = {}; + + if (buf.length === 32) { + return PrivateKey._transformBNBuffer(buf, network); + } + + info.network = Networks.get(buf[0], 'privatekey'); + + if (!info.network) { + throw new Error('Invalid network'); + } + + if (network && info.network !== Networks.get(network)) { + throw new TypeError('Private key network mismatch'); + } + + if (buf.length === 1 + 32 + 1 && buf[1 + 32 + 1 - 1] === 1) { + info.compressed = true; + } else if (buf.length === 1 + 32) { + info.compressed = false; + } else { + throw new Error('Length of buffer must be 33 (uncompressed) or 34 (compressed)'); + } + + info.bn = BN.fromBuffer(buf.slice(1, 32 + 1)); + + return info; +}; + +/** + * Internal function to transform a BN buffer into a private key + * + * @param {Buffer} buf + * @param {Network|string=} network - a {@link Network} object, or a string with the network name + * @returns {object} an Object with keys: bn, network, and compressed + * @private + */ +PrivateKey._transformBNBuffer = function (buf, network) { + var info = {}; + info.network = Networks.get(network) || Networks.defaultNetwork; + info.bn = BN.fromBuffer(buf); + info.compressed = false; + return info; +}; + +/** + * Internal function to transform a WIF string into a private key + * + * @param {string} buf - An WIF string + * @returns {Object} An object with keys: bn, network and compressed + * @private + */ +PrivateKey._transformWIF = function (str, network) { + return PrivateKey._transformBuffer(Base58Check.decode(str), network); +}; + +/** + * Instantiate a PrivateKey from a Buffer with the DER or WIF representation + * + * @param {Buffer} arg + * @param {Network} network + * @return {PrivateKey} + */ +PrivateKey.fromBuffer = function (arg, network) { + return new PrivateKey(arg, network); +}; + +/** + * Internal function to transform a JSON string on plain object into a private key + * return this. + * + * @param {string} json - A JSON string or plain object + * @returns {Object} An object with keys: bn, network and compressed + * @private + */ +PrivateKey._transformObject = function (json) { + var bn = new BN(json.bn, 'hex'); + var network = Networks.get(json.network); + return { + bn: bn, + network: network, + compressed: json.compressed + }; +}; + +/** + * Instantiate a PrivateKey from a WIF string + * + * @param {string} str - The WIF encoded private key string + * @returns {PrivateKey} A new valid instance of PrivateKey + */ +PrivateKey.fromString = PrivateKey.fromWIF = function (str) { + $.checkArgument(_.isString(str), 'First argument is expected to be a string.'); + return new PrivateKey(str); +}; + +/** + * Instantiate a PrivateKey from a plain JavaScript object + * + * @param {Object} obj - The output from privateKey.toObject() + */ +PrivateKey.fromObject = function (obj) { + $.checkArgument(_.isObject(obj), 'First argument is expected to be an object.'); + return new PrivateKey(obj); +}; + +/** + * Instantiate a PrivateKey from random bytes + * + * @param {string=} network - Either "livenet" or "testnet" + * @returns {PrivateKey} A new valid instance of PrivateKey + */ +PrivateKey.fromRandom = function (network) { + var bn = PrivateKey._getRandomBN(); + return new PrivateKey(bn, network); +}; + +/** + * Check if there would be any errors when initializing a PrivateKey + * + * @param {string} data - The encoded data in various formats + * @param {string=} network - Either "livenet" or "testnet" + * @returns {null|Error} An error if exists + */ + +PrivateKey.getValidationError = function (data, network) { + var error; + try { + /* jshint nonew: false */ + new PrivateKey(data, network); + } catch (e) { + error = e; + } + return error; +}; + +/** + * Check if the parameters are valid + * + * @param {string} data - The encoded data in various formats + * @param {string=} network - Either "livenet" or "testnet" + * @returns {Boolean} If the private key is would be valid + */ +PrivateKey.isValid = function (data, network) { + if (!data) { + return false; + } + return !PrivateKey.getValidationError(data, network); +}; + +/** + * Will output the PrivateKey encoded as hex string + * + * @returns {string} + */ +PrivateKey.prototype.toString = function () { + return this.toBuffer().toString('hex'); +}; + +/** + * Will output the PrivateKey to a WIF string + * + * @returns {string} A WIP representation of the private key + */ +PrivateKey.prototype.toWIF = function () { + var network = this.network; + var compressed = this.compressed; + + var buf; + if (compressed) { + buf = Buffer.concat([Buffer.from([network.privatekey]), + this.bn.toBuffer({ size: 32 }), + Buffer.from([0x01])]); + } else { + buf = Buffer.concat([Buffer.from([network.privatekey]), + this.bn.toBuffer({ size: 32 })]); + } + + return Base58Check.encode(buf); +}; + +/** + * Will return the private key as a BN instance + * + * @returns {BN} A BN instance of the private key + */ +PrivateKey.prototype.toBigNumber = function () { + return this.bn; +}; + +/** + * Will return the private key as a BN buffer + * + * @returns {Buffer} A buffer of the private key + */ +PrivateKey.prototype.toBuffer = function () { + // TODO: use `return this.bn.toBuffer({ size: 32 })` in v1.0.0 + return this.bn.toBuffer(); +}; + +/** + * WARNING: This method will not be officially supported until v1.0.0. + * + * + * Will return the private key as a BN buffer without leading zero padding + * + * @returns {Buffer} A buffer of the private key + */ +PrivateKey.prototype.toBufferNoPadding = function () { + return this.bn.toBuffer(); +}; + +/** + * Will return the corresponding public key + * + * @returns {PublicKey} A public key generated from the private key + */ +PrivateKey.prototype.toPublicKey = function () { + if (!this._pubkey) { + this._pubkey = PublicKey.fromPrivateKey(this); + } + return this._pubkey; +}; + +/** + * Will return an address for the private key + * @param {Network=} network - optional parameter specifying + * the desired network for the address + * + * @returns {Address} An address generated from the private key + */ +PrivateKey.prototype.toAddress = function (network) { + var pubkey = this.toPublicKey(); + return Address.fromPublicKey(pubkey, network || this.network); +}; + +/** + * @returns {Object} A plain object representation + */ +PrivateKey.prototype.toObject = PrivateKey.prototype.toJSON = function toObject() { + return { + bn: this.bn.toString('hex'), + compressed: this.compressed, + network: this.network.toString() + }; +}; + +/** + * Will return a string formatted for the console + * + * @returns {string} Private key + */ +PrivateKey.prototype.inspect = function () { + var uncompressed = !this.compressed ? ', uncompressed' : ''; + return ''; +}; + +export default PrivateKey; diff --git a/src/backend/wallet/lib/publickey.js b/src/backend/wallet/lib/publickey.js new file mode 100644 index 0000000..4768351 --- /dev/null +++ b/src/backend/wallet/lib/publickey.js @@ -0,0 +1,394 @@ +'use strict'; + +import BN from './crypto/bn'; +import Point from './crypto/point'; +import Hash from './crypto/hash'; +import JSUtil from './util/js'; +import Network from './networks'; +import _ from 'lodash'; +import $ from './util/preconditions'; +import address from './address'; +import Privatekey from './privatekey'; + +/** + * Instantiate a PublicKey from a {@link PrivateKey}, {@link Point}, `string`, or `Buffer`. + * + * There are two internal properties, `network` and `compressed`, that deal with importing + * a PublicKey from a PrivateKey in WIF format. More details described on {@link PrivateKey} + * + * @example + * ```javascript + * // instantiate from a private key + * var key = PublicKey(privateKey, true); + * + * // export to as a DER hex encoded string + * var exported = key.toString(); + * + * // import the public key + * var imported = PublicKey.fromString(exported); + * ``` + * + * @param {string} data - The encoded data in various formats + * @param {Object} extra - additional options + * @param {Network=} extra.network - Which network should the address for this public key be for + * @param {String=} extra.compressed - If the public key is compressed + * @returns {PublicKey} A new valid instance of an PublicKey + * @constructor + */ +function PublicKey(data, extra) { + + if (!(this instanceof PublicKey)) { + return new PublicKey(data, extra); + } + + $.checkArgument(data, 'First argument is required, please include public key data.'); + + if (data instanceof PublicKey) { + // Return copy, but as it's an immutable object, return same argument + return data; + } + extra = extra || {}; + + var info = this._classifyArgs(data, extra); + + // validation + info.point.validate(); + + JSUtil.defineImmutable(this, { + point: info.point, + compressed: info.compressed, + network: info.network || Network.defaultNetwork + }); + + return this; +}; + +/** + * Internal function to differentiate between arguments passed to the constructor + * @param {*} data + * @param {Object} extra + */ +PublicKey.prototype._classifyArgs = function (data, extra) { + /* jshint maxcomplexity: 10 */ + var info = { + compressed: _.isUndefined(extra.compressed) || extra.compressed + }; + + // detect type of data + if (data instanceof Point) { + info.point = data; + } else if (data.x && data.y) { + info = PublicKey._transformObject(data); + } else if (typeof (data) === 'string') { + info = PublicKey._transformDER(Buffer.from(data, 'hex')); + } else if (PublicKey._isBuffer(data)) { + info = PublicKey._transformDER(data); + } else if (PublicKey._isPrivateKey(data)) { + info = PublicKey._transformPrivateKey(data); + } else { + throw new TypeError('First argument is an unrecognized data format.'); + } + if (!info.network) { + info.network = _.isUndefined(extra.network) ? undefined : Network.get(extra.network); + } + return info; +}; + +/** + * Internal function to detect if an object is a {@link PrivateKey} + * + * @param {*} param - object to test + * @returns {boolean} + * @private + */ +PublicKey._isPrivateKey = function (param) { + return param instanceof Privatekey; +}; + +/** + * Internal function to detect if an object is a Buffer + * + * @param {*} param - object to test + * @returns {boolean} + * @private + */ +PublicKey._isBuffer = function (param) { + return (param instanceof Buffer) || (param instanceof Uint8Array); +}; + +/** + * Internal function to transform a private key into a public key point + * + * @param {PrivateKey} privkey - An instance of PrivateKey + * @returns {Object} An object with keys: point and compressed + * @private + */ +PublicKey._transformPrivateKey = function (privkey) { + $.checkArgument(PublicKey._isPrivateKey(privkey), 'Must be an instance of PrivateKey'); + var info = {}; + info.point = Point.getG().mul(privkey.bn); + info.compressed = privkey.compressed; + info.network = privkey.network; + return info; +}; + +/** + * Internal function to transform DER into a public key point + * + * @param {Buffer} buf - An hex encoded buffer + * @param {bool=} strict - if set to false, will loosen some conditions + * @returns {Object} An object with keys: point and compressed + * @private + */ +PublicKey._transformDER = function (buf, strict) { + /* jshint maxstatements: 30 */ + /* jshint maxcomplexity: 12 */ + $.checkArgument(PublicKey._isBuffer(buf), 'Must be a hex buffer of DER encoded public key'); + var info = {}; + + strict = _.isUndefined(strict) ? true : strict; + + var x; + var y; + var xbuf; + var ybuf; + + if (buf[0] === 0x04 || (!strict && (buf[0] === 0x06 || buf[0] === 0x07))) { + xbuf = buf.slice(1, 33); + ybuf = buf.slice(33, 65); + if (xbuf.length !== 32 || ybuf.length !== 32 || buf.length !== 65) { + throw new TypeError('Length of x and y must be 32 bytes'); + } + x = new BN(xbuf); + y = new BN(ybuf); + info.point = new Point(x, y); + info.compressed = false; + } else if (buf[0] === 0x03) { + xbuf = buf.slice(1); + x = new BN(xbuf); + info = PublicKey._transformX(true, x); + info.compressed = true; + } else if (buf[0] === 0x02) { + xbuf = buf.slice(1); + x = new BN(xbuf); + info = PublicKey._transformX(false, x); + info.compressed = true; + } else { + throw new TypeError('Invalid DER format public key'); + } + return info; +}; + +/** + * Internal function to transform X into a public key point + * + * @param {Boolean} odd - If the point is above or below the x axis + * @param {Point} x - The x point + * @returns {Object} An object with keys: point and compressed + * @private + */ +PublicKey._transformX = function (odd, x) { + $.checkArgument(typeof odd === 'boolean', 'Must specify whether y is odd or not (true or false)'); + var info = {}; + info.point = Point.fromX(odd, x); + return info; +}; + +/** + * Internal function to transform a JSON into a public key point + * + * @param {String|Object} json - a JSON string or plain object + * @returns {Object} An object with keys: point and compressed + * @private + */ +PublicKey._transformObject = function (json) { + var x = new BN(json.x, 'hex'); + var y = new BN(json.y, 'hex'); + var point = new Point(x, y); + return new PublicKey(point, { + compressed: json.compressed + }); +}; + +/** + * Instantiate a PublicKey from a PrivateKey + * + * @param {PrivateKey} privkey - An instance of PrivateKey + * @returns {PublicKey} A new valid instance of PublicKey + */ +PublicKey.fromPrivateKey = function (privkey) { + $.checkArgument(PublicKey._isPrivateKey(privkey), 'Must be an instance of PrivateKey'); + var info = PublicKey._transformPrivateKey(privkey); + return new PublicKey(info.point, { + compressed: info.compressed, + network: info.network + }); +}; + +/** + * Instantiate a PublicKey from a Buffer + * @param {Buffer} buf - A DER hex buffer + * @param {bool=} strict - if set to false, will loosen some conditions + * @returns {PublicKey} A new valid instance of PublicKey + */ +PublicKey.fromDER = PublicKey.fromBuffer = function (buf, strict) { + $.checkArgument(PublicKey._isBuffer(buf), 'Must be a hex buffer of DER encoded public key'); + var info = PublicKey._transformDER(buf, strict); + return new PublicKey(info.point, { + compressed: info.compressed + }); +}; + +/** + * Instantiate a PublicKey from a Point + * + * @param {Point} point - A Point instance + * @param {boolean=} compressed - whether to store this public key as compressed format + * @returns {PublicKey} A new valid instance of PublicKey + */ +PublicKey.fromPoint = function (point, compressed) { + $.checkArgument(point instanceof Point, 'First argument must be an instance of Point.'); + return new PublicKey(point, { + compressed: compressed + }); +}; + +/** + * Instantiate a PublicKey from a DER hex encoded string + * + * @param {string} str - A DER hex string + * @param {String=} encoding - The type of string encoding + * @returns {PublicKey} A new valid instance of PublicKey + */ +PublicKey.fromString = function (str, encoding) { + var buf = Buffer.from(str, encoding || 'hex'); + var info = PublicKey._transformDER(buf); + return new PublicKey(info.point, { + compressed: info.compressed + }); +}; + +/** + * Instantiate a PublicKey from an X Point + * + * @param {Boolean} odd - If the point is above or below the x axis + * @param {Point} x - The x point + * @returns {PublicKey} A new valid instance of PublicKey + */ +PublicKey.fromX = function (odd, x) { + var info = PublicKey._transformX(odd, x); + return new PublicKey(info.point, { + compressed: info.compressed + }); +}; + +/** + * Check if there would be any errors when initializing a PublicKey + * + * @param {string} data - The encoded data in various formats + * @returns {null|Error} An error if exists + */ +PublicKey.getValidationError = function (data) { + var error; + try { + /* jshint nonew: false */ + new PublicKey(data); + } catch (e) { + error = e; + } + return error; +}; + +/** + * Check if the parameters are valid + * + * @param {string} data - The encoded data in various formats + * @returns {Boolean} If the public key would be valid + */ +PublicKey.isValid = function (data) { + return !PublicKey.getValidationError(data); +}; + +/** + * @returns {Object} A plain object of the PublicKey + */ +PublicKey.prototype.toObject = PublicKey.prototype.toJSON = function toObject() { + return { + x: this.point.getX().toString('hex', 2), + y: this.point.getY().toString('hex', 2), + compressed: this.compressed + }; +}; + +/** + * Will output the PublicKey to a DER Buffer + * + * @returns {Buffer} A DER hex encoded buffer + */ +PublicKey.prototype.toBuffer = PublicKey.prototype.toDER = function () { + var x = this.point.getX(); + var y = this.point.getY(); + + var xbuf = x.toBuffer({ + size: 32 + }); + var ybuf = y.toBuffer({ + size: 32 + }); + + var prefix; + if (!this.compressed) { + prefix = Buffer.from([0x04]); + return Buffer.concat([prefix, xbuf, ybuf]); + } else { + var odd = ybuf[ybuf.length - 1] % 2; + if (odd) { + prefix = Buffer.from([0x03]); + } else { + prefix = Buffer.from([0x02]); + } + return Buffer.concat([prefix, xbuf]); + } +}; + +/** + * Will return a sha256 + ripemd160 hash of the serialized public key + * @see https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.h#L141 + * @returns {Buffer} + */ +PublicKey.prototype._getID = function _getID() { + return Hash.sha256ripemd160(this.toBuffer()); +}; + +/** + * Will return an address for the public key + * + * @param {String|Network=} network - Which network should the address be for + * @returns {Address} An address generated from the public key + */ +PublicKey.prototype.toAddress = function (network) { + var Address = address + return Address.fromPublicKey(this, network || this.network); +}; + +/** + * Will output the PublicKey to a DER encoded hex string + * + * @returns {string} A DER hex encoded string + */ +PublicKey.prototype.toString = function () { + return this.toDER().toString('hex'); +}; + +/** + * Will return a string formatted for the console + * + * @returns {string} Public key + */ +PublicKey.prototype.inspect = function () { + return ''; +}; + + +export default PublicKey; diff --git a/src/backend/wallet/lib/script/index.js b/src/backend/wallet/lib/script/index.js new file mode 100644 index 0000000..9560578 --- /dev/null +++ b/src/backend/wallet/lib/script/index.js @@ -0,0 +1,2 @@ +export { default } from './script' +export { default as Interpreter } from './interpreter' diff --git a/src/backend/wallet/lib/script/interpreter.js b/src/backend/wallet/lib/script/interpreter.js new file mode 100644 index 0000000..a789201 --- /dev/null +++ b/src/backend/wallet/lib/script/interpreter.js @@ -0,0 +1,1399 @@ +'use strict'; + +import _ from 'lodash'; + +import Script from './script'; +import Opcode from '../opcode'; +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import Signature from '../crypto/signature'; +import PublicKey from '../publickey'; +import transaction from '../transaction'; +/** + * Bitcoin transactions contain scripts. Each input has a script called the + * scriptSig, and each output has a script called the scriptPubkey. To validate + * an input, the input's script is concatenated with the referenced output script, + * and the result is executed. If at the end of execution the stack contains a + * "true" value, then the transaction is valid. + * + * The primary way to use this class is via the verify function. + * e.g., Interpreter().verify( ... ); + */ +var Interpreter = function Interpreter(obj) { + if (!(this instanceof Interpreter)) { + return new Interpreter(obj); + } + if (obj) { + this.initialize(); + this.set(obj); + } else { + this.initialize(); + } +}; + +Interpreter.prototype.verifyWitnessProgram = function (version, program, witness, satoshis, flags) { + var scriptPubKey = new Script(); + var stack = []; + + if (version === 0) { + if (program.length === 32) { + if (witness.length === 0) { + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY'; + return false; + } + + var scriptPubKeyBuffer = witness[witness.length - 1]; + scriptPubKey = new Script(scriptPubKeyBuffer); + var hash = Hash.sha256(scriptPubKeyBuffer); + if (hash.toString('hex') !== program.toString('hex')) { + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; + return false; + } + + stack = witness.slice(0, -1); + } else if (program.length === 20) { + if (witness.length !== 2) { + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; + return false; + } + + scriptPubKey.add(Opcode.OP_DUP); + scriptPubKey.add(Opcode.OP_HASH160); + scriptPubKey.add(program); + scriptPubKey.add(Opcode.OP_EQUALVERIFY); + scriptPubKey.add(Opcode.OP_CHECKSIG); + + stack = witness; + + } else { + this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH'; + return false; + } + } else if ((flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'; + return false; + } else { + return true; + } + + this.initialize(); + + this.set({ + script: scriptPubKey, + stack: stack, + sigversion: 1, + satoshis: satoshis + }); + + if (!this.evaluate()) { + return false; + } + + if (this.stack.length !== 1) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } + + var buf = this.stack[this.stack.length - 1]; + if (!Interpreter.castToBool(buf)) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; + return false; + } + + return true; +}; + + + +/** + * Verifies a Script by executing it and returns true if it is valid. + * This function needs to be provided with the scriptSig and the scriptPubkey + * separately. + * @param {Script} scriptSig - the script's first part (corresponding to the tx input) + * @param {Script} scriptPubkey - the script's last part (corresponding to the tx output) + * @param {Transaction=} tx - the Transaction containing the scriptSig in one input (used + * to check signature validity for some opcodes like OP_CHECKSIG) + * @param {number} nin - index of the transaction input containing the scriptSig verified. + * @param {number} flags - evaluation flags. See Interpreter.SCRIPT_* constants + * @param {number} witness - array of witness data + * @param {number} satoshis - number of satoshis created by this output + * + * Translated from bitcoind's VerifyScript + */ +Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) { + + const Transaction = transaction; + if (_.isUndefined(tx)) { + tx = new Transaction(); + } + if (_.isUndefined(nin)) { + nin = 0; + } + if (_.isUndefined(flags)) { + flags = 0; + } + if (_.isUndefined(witness)) { + witness = null; + } + if (_.isUndefined(satoshis)) { + satoshis = 0; + } + + this.set({ + script: scriptSig, + tx: tx, + nin: nin, + sigversion: 0, + satoshis: 0, + flags: flags + }); + var stackCopy; + + if ((flags & Interpreter.SCRIPT_VERIFY_SIGPUSHONLY) !== 0 && !scriptSig.isPushOnly()) { + this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; + return false; + } + + // evaluate scriptSig + if (!this.evaluate()) { + return false; + } + + if (flags & Interpreter.SCRIPT_VERIFY_P2SH) { + stackCopy = this.stack.slice(); + } + + var stack = this.stack; + this.initialize(); + this.set({ + script: scriptPubkey, + stack: stack, + tx: tx, + nin: nin, + flags: flags + }); + + // evaluate scriptPubkey + if (!this.evaluate()) { + return false; + } + + if (this.stack.length === 0) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_RESULT'; + return false; + } + + var buf = this.stack[this.stack.length - 1]; + if (!Interpreter.castToBool(buf)) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; + return false; + } + + var hadWitness = false; + + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + var witnessValues = {}; + if (scriptPubkey.isWitnessProgram(witnessValues)) { + hadWitness = true; + if (scriptSig.toBuffer().length !== 0) { + return false; + } + + if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.program, witness, satoshis, flags)) { + return false; + } + } + } + + // Additional validation for spend-to-script-hash transactions: + if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) { + // scriptSig must be literals-only or validation fails + if (!scriptSig.isPushOnly()) { + this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; + return false; + } + + // stackCopy cannot be empty here, because if it was the + // P2SH HASH <> EQUAL scriptPubKey would be evaluated with + // an empty stack and the EvalScript above would return false. + if (stackCopy.length === 0) { + throw new Error('internal error - stack copy empty'); + } + + var redeemScriptSerialized = stackCopy[stackCopy.length - 1]; + var redeemScript = Script.fromBuffer(redeemScriptSerialized); + stackCopy.pop(); + + this.initialize(); + this.set({ + script: redeemScript, + stack: stackCopy, + tx: tx, + nin: nin, + flags: flags + }); + + // evaluate redeemScript + if (!this.evaluate()) { + return false; + } + + if (stackCopy.length === 0) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK'; + return false; + } + + if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; + return false; + } + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + var p2shWitnessValues = {}; + if (redeemScript.isWitnessProgram(p2shWitnessValues)) { + hadWitness = true; + var redeemScriptPush = new Script(); + redeemScriptPush.add(redeemScript.toBuffer()); + if (scriptSig.toHex() !== redeemScriptPush.toHex()) { + this.errstr = 'SCRIPT_ERR_WITNESS_MALLEATED_P2SH'; + return false; + } + + if (!this.verifyWitnessProgram(p2shWitnessValues.version, p2shWitnessValues.program, witness, satoshis, flags)) { + return false; + } + + stack = [stack[0]]; + } + } + + if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { + if (!hadWitness && witness.length > 0) { + this.errstr = 'SCRIPT_ERR_WITNESS_UNEXPECTED'; + return false; + } + } + } + + return true; +}; + +export default Interpreter; + +Interpreter.prototype.initialize = function (obj) { + this.stack = []; + this.altstack = []; + this.pc = 0; + this.satoshis = 0; + this.sigversion = 0; + this.pbegincodehash = 0; + this.nOpCount = 0; + this.vfExec = []; + this.errstr = ''; + this.flags = 0; +}; + +Interpreter.prototype.set = function (obj) { + this.script = obj.script || this.script; + this.tx = obj.tx || this.tx; + this.nin = typeof obj.nin !== 'undefined' ? obj.nin : this.nin; + this.stack = obj.stack || this.stack; + this.altstack = obj.altack || this.altstack; + this.pc = typeof obj.pc !== 'undefined' ? obj.pc : this.pc; + this.pbegincodehash = typeof obj.pbegincodehash !== 'undefined' ? obj.pbegincodehash : this.pbegincodehash; + this.sigversion = typeof obj.sigversion !== 'undefined' ? obj.sigversion : this.sigversion; + this.satoshis = typeof obj.satoshis !== 'undefined' ? obj.satoshis : this.satoshis; + this.nOpCount = typeof obj.nOpCount !== 'undefined' ? obj.nOpCount : this.nOpCount; + this.vfExec = obj.vfExec || this.vfExec; + this.errstr = obj.errstr || this.errstr; + this.flags = typeof obj.flags !== 'undefined' ? obj.flags : this.flags; +}; + +Interpreter.true = Buffer.from([1]); +Interpreter.false = Buffer.from([]); + +Interpreter.MAX_SCRIPT_ELEMENT_SIZE = 520; + +Interpreter.LOCKTIME_THRESHOLD = 500000000; +Interpreter.LOCKTIME_THRESHOLD_BN = new BN(Interpreter.LOCKTIME_THRESHOLD); + +// flags taken from bitcoind +// bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 +Interpreter.SCRIPT_VERIFY_NONE = 0; + +// Making v1-v16 witness program non-standard +Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = (1 << 12); + +// Evaluate P2SH subscripts (softfork safe, BIP16). +Interpreter.SCRIPT_VERIFY_P2SH = (1 << 0); + +// Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure. +// Passing a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) to checksig causes that pubkey to be +// skipped (not softfork safe: this flag can widen the validity of OP_CHECKSIG OP_NOT). +Interpreter.SCRIPT_VERIFY_STRICTENC = (1 << 1); + +// Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1) +Interpreter.SCRIPT_VERIFY_DERSIG = (1 << 2); + +// Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure +// (softfork safe, BIP62 rule 5). +Interpreter.SCRIPT_VERIFY_LOW_S = (1 << 3); + +// verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7). +Interpreter.SCRIPT_VERIFY_NULLDUMMY = (1 << 4); + +// Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2). +Interpreter.SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5); + +// Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct +// pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating +// any other push causes the script to fail (BIP62 rule 3). +// In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4). +// (softfork safe) +Interpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6); + +// Discourage use of NOPs reserved for upgrades (NOP1-10) +// +// Provided so that nodes can avoid accepting or mining transactions +// containing executed NOP's whose meaning may change after a soft-fork, +// thus rendering the script invalid; with this flag set executing +// discouraged NOPs fails the script. This verification flag will never be +// a mandatory flag applied to scripts in a block. NOPs that are not +// executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected. +Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); + +// CLTV See BIP65 for details. +Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9); +Interpreter.SCRIPT_VERIFY_WITNESS = (1 << 10); +Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 11); + +Interpreter.castToBool = function (buf) { + for (var i = 0; i < buf.length; i++) { + if (buf[i] !== 0) { + // can be negative zero + if (i === buf.length - 1 && buf[i] === 0x80) { + return false; + } + return true; + } + } + return false; +}; + +/** + * Translated from bitcoind's CheckSignatureEncoding + */ +Interpreter.prototype.checkSignatureEncoding = function (buf) { + var sig; + if ((this.flags & (Interpreter.SCRIPT_VERIFY_DERSIG | Interpreter.SCRIPT_VERIFY_LOW_S | Interpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isTxDER(buf)) { + this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT'; + return false; + } else if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) { + sig = Signature.fromTxFormat(buf); + if (!sig.hasLowS()) { + this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S'; + return false; + } + } else if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) { + sig = Signature.fromTxFormat(buf); + if (!sig.hasDefinedHashtype()) { + this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE'; + return false; + } + } + return true; +}; + +/** + * Translated from bitcoind's CheckPubKeyEncoding + */ +Interpreter.prototype.checkPubkeyEncoding = function (buf) { + if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) { + this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; + return false; + } + return true; +}; + +/** + * Based on bitcoind's EvalScript function, with the inner loop moved to + * Interpreter.prototype.step() + * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 + */ +Interpreter.prototype.evaluate = function () { + if (this.script.toBuffer().length > 10000) { + this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE'; + return false; + } + + try { + while (this.pc < this.script.chunks.length) { + var fSuccess = this.step(); + if (!fSuccess) { + return false; + } + } + + // Size limits + if (this.stack.length + this.altstack.length > 1000) { + this.errstr = 'SCRIPT_ERR_STACK_SIZE'; + return false; + } + } catch (e) { + this.errstr = 'SCRIPT_ERR_UNKNOWN_ERROR: ' + e; + return false; + } + + if (this.vfExec.length > 0) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + + return true; +}; + +/** + * Checks a locktime parameter with the transaction's locktime. + * There are two times of nLockTime: lock-by-blockheight and lock-by-blocktime, + * distinguished by whether nLockTime < LOCKTIME_THRESHOLD = 500000000 + * + * See the corresponding code on bitcoin core: + * https://github.com/bitcoin/bitcoin/blob/ffd75adce01a78b3461b3ff05bcc2b530a9ce994/src/script/interpreter.cpp#L1129 + * + * @param {BN} nLockTime the locktime read from the script + * @return {boolean} true if the transaction's locktime is less than or equal to + * the transaction's locktime + */ +Interpreter.prototype.checkLockTime = function (nLockTime) { + + // We want to compare apples to apples, so fail the script + // unless the type of nLockTime being tested is the same as + // the nLockTime in the transaction. + if (!( + (this.tx.nLockTime < Interpreter.LOCKTIME_THRESHOLD && nLockTime.lt(Interpreter.LOCKTIME_THRESHOLD_BN)) || + (this.tx.nLockTime >= Interpreter.LOCKTIME_THRESHOLD && nLockTime.gte(Interpreter.LOCKTIME_THRESHOLD_BN)) + )) { + return false; + } + + // Now that we know we're comparing apples-to-apples, the + // comparison is a simple numeric one. + if (nLockTime.gt(new BN(this.tx.nLockTime))) { + return false; + } + + // Finally the nLockTime feature can be disabled and thus + // CHECKLOCKTIMEVERIFY bypassed if every txin has been + // finalized by setting nSequence to maxint. The + // transaction would be allowed into the blockchain, making + // the opcode ineffective. + // + // Testing if this vin is not final is sufficient to + // prevent this condition. Alternatively we could test all + // inputs, but testing just this input minimizes the data + // required to prove correct CHECKLOCKTIMEVERIFY execution. + if (!this.tx.inputs[this.nin].isFinal()) { + return false; + } + + return true; +} + +/** + * Based on the inner loop of bitcoind's EvalScript function + * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 + */ +Interpreter.prototype.step = function () { + + var fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; + + //bool fExec = !count(vfExec.begin(), vfExec.end(), false); + var fExec = (this.vfExec.indexOf(false) === -1); + var buf, buf1, buf2, spliced, n, x1, x2, bn, bn1, bn2, bufSig, bufPubkey, subscript; + var sig, pubkey; + var fValue, fSuccess; + + // Read instruction + var chunk = this.script.chunks[this.pc]; + this.pc++; + var opcodenum = chunk.opcodenum; + if (_.isUndefined(opcodenum)) { + this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE'; + return false; + } + if (chunk.buf && chunk.buf.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE) { + this.errstr = 'SCRIPT_ERR_PUSH_SIZE'; + return false; + } + + // Note how Opcode.OP_RESERVED does not count towards the opcode limit. + if (opcodenum > Opcode.OP_16 && ++(this.nOpCount) > 201) { + this.errstr = 'SCRIPT_ERR_OP_COUNT'; + return false; + } + + + if (opcodenum === Opcode.OP_CAT || + opcodenum === Opcode.OP_SUBSTR || + opcodenum === Opcode.OP_LEFT || + opcodenum === Opcode.OP_RIGHT || + opcodenum === Opcode.OP_INVERT || + opcodenum === Opcode.OP_AND || + opcodenum === Opcode.OP_OR || + opcodenum === Opcode.OP_XOR || + opcodenum === Opcode.OP_2MUL || + opcodenum === Opcode.OP_2DIV || + opcodenum === Opcode.OP_MUL || + opcodenum === Opcode.OP_DIV || + opcodenum === Opcode.OP_MOD || + opcodenum === Opcode.OP_LSHIFT || + opcodenum === Opcode.OP_RSHIFT) { + this.errstr = 'SCRIPT_ERR_DISABLED_OPCODE'; + return false; + } + + if (fExec && 0 <= opcodenum && opcodenum <= Opcode.OP_PUSHDATA4) { + if (fRequireMinimal && !this.script.checkMinimalPush(this.pc - 1)) { + this.errstr = 'SCRIPT_ERR_MINIMALDATA'; + return false; + } + if (!chunk.buf) { + this.stack.push(Interpreter.false); + } else if (chunk.len !== chunk.buf.length) { + throw new Error('Length of push value not equal to length of data'); + } else { + this.stack.push(chunk.buf); + } + } else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) { + switch (opcodenum) { + // Push value + case Opcode.OP_1NEGATE: + case Opcode.OP_1: + case Opcode.OP_2: + case Opcode.OP_3: + case Opcode.OP_4: + case Opcode.OP_5: + case Opcode.OP_6: + case Opcode.OP_7: + case Opcode.OP_8: + case Opcode.OP_9: + case Opcode.OP_10: + case Opcode.OP_11: + case Opcode.OP_12: + case Opcode.OP_13: + case Opcode.OP_14: + case Opcode.OP_15: + case Opcode.OP_16: + { + // ( -- value) + // ScriptNum bn((int)opcode - (int)(Opcode.OP_1 - 1)); + n = opcodenum - (Opcode.OP_1 - 1); + buf = new BN(n).toScriptNumBuffer(); + this.stack.push(buf); + // The result of these opcodes should always be the minimal way to push the data + // they push, so no need for a CheckMinimalPush here. + } + break; + + + // + // Control + // + case Opcode.OP_NOP: + break; + + case Opcode.OP_NOP2: + case Opcode.OP_CHECKLOCKTIMEVERIFY: + + if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { + // not enabled; treat as a NOP2 + if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; + return false; + } + break; + } + + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + // Note that elsewhere numeric opcodes are limited to + // operands in the range -2**31+1 to 2**31-1, however it is + // legal for opcodes to produce results exceeding that + // range. This limitation is implemented by CScriptNum's + // default 4-byte limit. + // + // If we kept to that limit we'd have a year 2038 problem, + // even though the nLockTime field in transactions + // themselves is uint32 which only becomes meaningless + // after the year 2106. + // + // Thus as a special case we tell CScriptNum to accept up + // to 5-byte bignums, which are good until 2**39-1, well + // beyond the 2**32-1 limit of the nLockTime field itself. + var nLockTime = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal, 5); + + // In the rare event that the argument may be < 0 due to + // some arithmetic being done first, you can always use + // 0 MAX CHECKLOCKTIMEVERIFY. + if (nLockTime.lt(new BN(0))) { + this.errstr = 'SCRIPT_ERR_NEGATIVE_LOCKTIME'; + return false; + } + + // Actually compare the specified lock time with the transaction. + if (!this.checkLockTime(nLockTime)) { + this.errstr = 'SCRIPT_ERR_UNSATISFIED_LOCKTIME'; + return false; + } + break; + + case Opcode.OP_NOP1: + case Opcode.OP_NOP3: + case Opcode.OP_NOP4: + case Opcode.OP_NOP5: + case Opcode.OP_NOP6: + case Opcode.OP_NOP7: + case Opcode.OP_NOP8: + case Opcode.OP_NOP9: + case Opcode.OP_NOP10: + { + if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; + return false; + } + } + break; + + case Opcode.OP_IF: + case Opcode.OP_NOTIF: + { + // if [statements] [else [statements]] endif + // bool fValue = false; + fValue = false; + if (fExec) { + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + buf = this.stack.pop(); + fValue = Interpreter.castToBool(buf); + if (opcodenum === Opcode.OP_NOTIF) { + fValue = !fValue; + } + } + this.vfExec.push(fValue); + } + break; + + case Opcode.OP_ELSE: + { + if (this.vfExec.length === 0) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + this.vfExec[this.vfExec.length - 1] = !this.vfExec[this.vfExec.length - 1]; + } + break; + + case Opcode.OP_ENDIF: + { + if (this.vfExec.length === 0) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + this.vfExec.pop(); + } + break; + + case Opcode.OP_VERIFY: + { + // (true -- ) or + // (false -- false) and return + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf = this.stack[this.stack.length - 1]; + fValue = Interpreter.castToBool(buf); + if (fValue) { + this.stack.pop(); + } else { + this.errstr = 'SCRIPT_ERR_VERIFY'; + return false; + } + } + break; + + case Opcode.OP_RETURN: + { + this.errstr = 'SCRIPT_ERR_OP_RETURN'; + return false; + } + break; + + + // + // Stack ops + // + case Opcode.OP_TOALTSTACK: + { + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.altstack.push(this.stack.pop()); + } + break; + + case Opcode.OP_FROMALTSTACK: + { + if (this.altstack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_ALTSTACK_OPERATION'; + return false; + } + this.stack.push(this.altstack.pop()); + } + break; + + case Opcode.OP_2DROP: + { + // (x1 x2 -- ) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.pop(); + this.stack.pop(); + } + break; + + case Opcode.OP_2DUP: + { + // (x1 x2 -- x1 x2 x1 x2) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf1 = this.stack[this.stack.length - 2]; + buf2 = this.stack[this.stack.length - 1]; + this.stack.push(buf1); + this.stack.push(buf2); + } + break; + + case Opcode.OP_3DUP: + { + // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) + if (this.stack.length < 3) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf1 = this.stack[this.stack.length - 3]; + buf2 = this.stack[this.stack.length - 2]; + var buf3 = this.stack[this.stack.length - 1]; + this.stack.push(buf1); + this.stack.push(buf2); + this.stack.push(buf3); + } + break; + + case Opcode.OP_2OVER: + { + // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) + if (this.stack.length < 4) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf1 = this.stack[this.stack.length - 4]; + buf2 = this.stack[this.stack.length - 3]; + this.stack.push(buf1); + this.stack.push(buf2); + } + break; + + case Opcode.OP_2ROT: + { + // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) + if (this.stack.length < 6) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + spliced = this.stack.splice(this.stack.length - 6, 2); + this.stack.push(spliced[0]); + this.stack.push(spliced[1]); + } + break; + + case Opcode.OP_2SWAP: + { + // (x1 x2 x3 x4 -- x3 x4 x1 x2) + if (this.stack.length < 4) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + spliced = this.stack.splice(this.stack.length - 4, 2); + this.stack.push(spliced[0]); + this.stack.push(spliced[1]); + } + break; + + case Opcode.OP_IFDUP: + { + // (x - 0 | x x) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf = this.stack[this.stack.length - 1]; + fValue = Interpreter.castToBool(buf); + if (fValue) { + this.stack.push(buf); + } + } + break; + + case Opcode.OP_DEPTH: + { + // -- stacksize + buf = new BN(this.stack.length).toScriptNumBuffer(); + this.stack.push(buf); + } + break; + + case Opcode.OP_DROP: + { + // (x -- ) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.pop(); + } + break; + + case Opcode.OP_DUP: + { + // (x -- x x) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.push(this.stack[this.stack.length - 1]); + } + break; + + case Opcode.OP_NIP: + { + // (x1 x2 -- x2) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.splice(this.stack.length - 2, 1); + } + break; + + case Opcode.OP_OVER: + { + // (x1 x2 -- x1 x2 x1) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.push(this.stack[this.stack.length - 2]); + } + break; + + case Opcode.OP_PICK: + case Opcode.OP_ROLL: + { + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf = this.stack[this.stack.length - 1]; + bn = BN.fromScriptNumBuffer(buf, fRequireMinimal); + n = bn.toNumber(); + this.stack.pop(); + if (n < 0 || n >= this.stack.length) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf = this.stack[this.stack.length - n - 1]; + if (opcodenum === Opcode.OP_ROLL) { + this.stack.splice(this.stack.length - n - 1, 1); + } + this.stack.push(buf); + } + break; + + case Opcode.OP_ROT: + { + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap + if (this.stack.length < 3) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + x1 = this.stack[this.stack.length - 3]; + x2 = this.stack[this.stack.length - 2]; + var x3 = this.stack[this.stack.length - 1]; + this.stack[this.stack.length - 3] = x2; + this.stack[this.stack.length - 2] = x3; + this.stack[this.stack.length - 1] = x1; + } + break; + + case Opcode.OP_SWAP: + { + // (x1 x2 -- x2 x1) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + x1 = this.stack[this.stack.length - 2]; + x2 = this.stack[this.stack.length - 1]; + this.stack[this.stack.length - 2] = x2; + this.stack[this.stack.length - 1] = x1; + } + break; + + case Opcode.OP_TUCK: + { + // (x1 x2 -- x2 x1 x2) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + this.stack.splice(this.stack.length - 2, 0, this.stack[this.stack.length - 1]); + } + break; + + + case Opcode.OP_SIZE: + { + // (in -- in size) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + bn = new BN(this.stack[this.stack.length - 1].length); + this.stack.push(bn.toScriptNumBuffer()); + } + break; + + + // + // Bitwise logic + // + case Opcode.OP_EQUAL: + case Opcode.OP_EQUALVERIFY: + //case Opcode.OP_NOTEQUAL: // use Opcode.OP_NUMNOTEQUAL + { + // (x1 x2 - bool) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf1 = this.stack[this.stack.length - 2]; + buf2 = this.stack[this.stack.length - 1]; + var fEqual = buf1.toString('hex') === buf2.toString('hex'); + this.stack.pop(); + this.stack.pop(); + this.stack.push(fEqual ? Interpreter.true : Interpreter.false); + if (opcodenum === Opcode.OP_EQUALVERIFY) { + if (fEqual) { + this.stack.pop(); + } else { + this.errstr = 'SCRIPT_ERR_EQUALVERIFY'; + return false; + } + } + } + break; + + + // + // Numeric + // + case Opcode.OP_1ADD: + case Opcode.OP_1SUB: + case Opcode.OP_NEGATE: + case Opcode.OP_ABS: + case Opcode.OP_NOT: + case Opcode.OP_0NOTEQUAL: + { + // (in -- out) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf = this.stack[this.stack.length - 1]; + bn = BN.fromScriptNumBuffer(buf, fRequireMinimal); + switch (opcodenum) { + case Opcode.OP_1ADD: + bn = bn.add(BN.One); + break; + case Opcode.OP_1SUB: + bn = bn.sub(BN.One); + break; + case Opcode.OP_NEGATE: + bn = bn.neg(); + break; + case Opcode.OP_ABS: + if (bn.cmp(BN.Zero) < 0) { + bn = bn.neg(); + } + break; + case Opcode.OP_NOT: + bn = new BN((bn.cmp(BN.Zero) === 0) + 0); + break; + case Opcode.OP_0NOTEQUAL: + bn = new BN((bn.cmp(BN.Zero) !== 0) + 0); + break; + //default: assert(!'invalid opcode'); break; // TODO: does this ever occur? + } + this.stack.pop(); + this.stack.push(bn.toScriptNumBuffer()); + } + break; + + case Opcode.OP_ADD: + case Opcode.OP_SUB: + case Opcode.OP_BOOLAND: + case Opcode.OP_BOOLOR: + case Opcode.OP_NUMEQUAL: + case Opcode.OP_NUMEQUALVERIFY: + case Opcode.OP_NUMNOTEQUAL: + case Opcode.OP_LESSTHAN: + case Opcode.OP_GREATERTHAN: + case Opcode.OP_LESSTHANOREQUAL: + case Opcode.OP_GREATERTHANOREQUAL: + case Opcode.OP_MIN: + case Opcode.OP_MAX: + { + // (x1 x2 -- out) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + bn1 = BN.fromScriptNumBuffer(this.stack[this.stack.length - 2], fRequireMinimal); + bn2 = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal); + bn = new BN(0); + + switch (opcodenum) { + case Opcode.OP_ADD: + bn = bn1.add(bn2); + break; + + case Opcode.OP_SUB: + bn = bn1.sub(bn2); + break; + + // case Opcode.OP_BOOLAND: bn = (bn1 != bnZero && bn2 != bnZero); break; + case Opcode.OP_BOOLAND: + bn = new BN(((bn1.cmp(BN.Zero) !== 0) && (bn2.cmp(BN.Zero) !== 0)) + 0); + break; + // case Opcode.OP_BOOLOR: bn = (bn1 != bnZero || bn2 != bnZero); break; + case Opcode.OP_BOOLOR: + bn = new BN(((bn1.cmp(BN.Zero) !== 0) || (bn2.cmp(BN.Zero) !== 0)) + 0); + break; + // case Opcode.OP_NUMEQUAL: bn = (bn1 == bn2); break; + case Opcode.OP_NUMEQUAL: + bn = new BN((bn1.cmp(bn2) === 0) + 0); + break; + // case Opcode.OP_NUMEQUALVERIFY: bn = (bn1 == bn2); break; + case Opcode.OP_NUMEQUALVERIFY: + bn = new BN((bn1.cmp(bn2) === 0) + 0); + break; + // case Opcode.OP_NUMNOTEQUAL: bn = (bn1 != bn2); break; + case Opcode.OP_NUMNOTEQUAL: + bn = new BN((bn1.cmp(bn2) !== 0) + 0); + break; + // case Opcode.OP_LESSTHAN: bn = (bn1 < bn2); break; + case Opcode.OP_LESSTHAN: + bn = new BN((bn1.cmp(bn2) < 0) + 0); + break; + // case Opcode.OP_GREATERTHAN: bn = (bn1 > bn2); break; + case Opcode.OP_GREATERTHAN: + bn = new BN((bn1.cmp(bn2) > 0) + 0); + break; + // case Opcode.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2); break; + case Opcode.OP_LESSTHANOREQUAL: + bn = new BN((bn1.cmp(bn2) <= 0) + 0); + break; + // case Opcode.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2); break; + case Opcode.OP_GREATERTHANOREQUAL: + bn = new BN((bn1.cmp(bn2) >= 0) + 0); + break; + case Opcode.OP_MIN: + bn = (bn1.cmp(bn2) < 0 ? bn1 : bn2); + break; + case Opcode.OP_MAX: + bn = (bn1.cmp(bn2) > 0 ? bn1 : bn2); + break; + // default: assert(!'invalid opcode'); break; //TODO: does this ever occur? + } + this.stack.pop(); + this.stack.pop(); + this.stack.push(bn.toScriptNumBuffer()); + + if (opcodenum === Opcode.OP_NUMEQUALVERIFY) { + // if (CastToBool(stacktop(-1))) + if (Interpreter.castToBool(this.stack[this.stack.length - 1])) { + this.stack.pop(); + } else { + this.errstr = 'SCRIPT_ERR_NUMEQUALVERIFY'; + return false; + } + } + } + break; + + case Opcode.OP_WITHIN: + { + // (x min max -- out) + if (this.stack.length < 3) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + bn1 = BN.fromScriptNumBuffer(this.stack[this.stack.length - 3], fRequireMinimal); + bn2 = BN.fromScriptNumBuffer(this.stack[this.stack.length - 2], fRequireMinimal); + var bn3 = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal); + //bool fValue = (bn2 <= bn1 && bn1 < bn3); + fValue = (bn2.cmp(bn1) <= 0) && (bn1.cmp(bn3) < 0); + this.stack.pop(); + this.stack.pop(); + this.stack.pop(); + this.stack.push(fValue ? Interpreter.true : Interpreter.false); + } + break; + + + // + // Crypto + // + case Opcode.OP_RIPEMD160: + case Opcode.OP_SHA1: + case Opcode.OP_SHA256: + case Opcode.OP_HASH160: + case Opcode.OP_HASH256: + { + // (in -- hash) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + buf = this.stack[this.stack.length - 1]; + //valtype vchHash((opcode == Opcode.OP_RIPEMD160 || + // opcode == Opcode.OP_SHA1 || opcode == Opcode.OP_HASH160) ? 20 : 32); + var bufHash; + if (opcodenum === Opcode.OP_RIPEMD160) { + bufHash = Hash.ripemd160(buf); + } else if (opcodenum === Opcode.OP_SHA1) { + bufHash = Hash.sha1(buf); + } else if (opcodenum === Opcode.OP_SHA256) { + bufHash = Hash.sha256(buf); + } else if (opcodenum === Opcode.OP_HASH160) { + bufHash = Hash.sha256ripemd160(buf); + } else if (opcodenum === Opcode.OP_HASH256) { + bufHash = Hash.sha256sha256(buf); + } + this.stack.pop(); + this.stack.push(bufHash); + } + break; + + case Opcode.OP_CODESEPARATOR: + { + // Hash starts after the code separator + this.pbegincodehash = this.pc; + } + break; + + case Opcode.OP_CHECKSIG: + case Opcode.OP_CHECKSIGVERIFY: + { + // (sig pubkey -- bool) + if (this.stack.length < 2) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + bufSig = this.stack[this.stack.length - 2]; + bufPubkey = this.stack[this.stack.length - 1]; + + // Subset of script starting at the most recent codeseparator + // CScript scriptCode(pbegincodehash, pend); + subscript = new Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }); + + // Drop the signature, since there's no way for a signature to sign itself + var tmpScript = new Script().add(bufSig); + subscript.findAndDelete(tmpScript); + + if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { + return false; + } + + try { + sig = Signature.fromTxFormat(bufSig); + pubkey = PublicKey.fromBuffer(bufPubkey, false); + fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis); + } catch (e) { + //invalid sig or pubkey + fSuccess = false; + } + + this.stack.pop(); + this.stack.pop(); + // stack.push_back(fSuccess ? vchTrue : vchFalse); + this.stack.push(fSuccess ? Interpreter.true : Interpreter.false); + if (opcodenum === Opcode.OP_CHECKSIGVERIFY) { + if (fSuccess) { + this.stack.pop(); + } else { + this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY'; + return false; + } + } + } + break; + + case Opcode.OP_CHECKMULTISIG: + case Opcode.OP_CHECKMULTISIGVERIFY: + { + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + var i = 1; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var nKeysCount = BN.fromScriptNumBuffer(this.stack[this.stack.length - i], fRequireMinimal).toNumber(); + if (nKeysCount < 0 || nKeysCount > 20) { + this.errstr = 'SCRIPT_ERR_PUBKEY_COUNT'; + return false; + } + this.nOpCount += nKeysCount; + if (this.nOpCount > 201) { + this.errstr = 'SCRIPT_ERR_OP_COUNT'; + return false; + } + // int ikey = ++i; + var ikey = ++i; + i += nKeysCount; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var nSigsCount = BN.fromScriptNumBuffer(this.stack[this.stack.length - i], fRequireMinimal).toNumber(); + if (nSigsCount < 0 || nSigsCount > nKeysCount) { + this.errstr = 'SCRIPT_ERR_SIG_COUNT'; + return false; + } + // int isig = ++i; + var isig = ++i; + i += nSigsCount; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + // Subset of script starting at the most recent codeseparator + subscript = new Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }); + + // Drop the signatures, since there's no way for a signature to sign itself + for (var k = 0; k < nSigsCount; k++) { + bufSig = this.stack[this.stack.length - isig - k]; + subscript.findAndDelete(new Script().add(bufSig)); + } + + fSuccess = true; + while (fSuccess && nSigsCount > 0) { + // valtype& vchSig = stacktop(-isig); + bufSig = this.stack[this.stack.length - isig]; + // valtype& vchPubKey = stacktop(-ikey); + bufPubkey = this.stack[this.stack.length - ikey]; + + if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { + return false; + } + + var fOk; + try { + sig = Signature.fromTxFormat(bufSig); + pubkey = PublicKey.fromBuffer(bufPubkey, false); + fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis); + } catch (e) { + //invalid sig or pubkey + fOk = false; + } + + if (fOk) { + isig++; + nSigsCount--; + } + ikey++; + nKeysCount--; + + // If there are more signatures left than keys left, + // then too many signatures have failed + if (nSigsCount > nKeysCount) { + fSuccess = false; + } + } + + // Clean up stack of actual arguments + while (i-- > 1) { + this.stack.pop(); + } + + // A bug causes CHECKMULTISIG to consume one extra argument + // whose contents were not checked in any way. + // + // Unfortunately this is a potential source of mutability, + // so optionally verify it is exactly equal to zero prior + // to removing it from the stack. + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + if ((this.flags & Interpreter.SCRIPT_VERIFY_NULLDUMMY) && this.stack[this.stack.length - 1].length) { + this.errstr = 'SCRIPT_ERR_SIG_NULLDUMMY'; + return false; + } + this.stack.pop(); + + this.stack.push(fSuccess ? Interpreter.true : Interpreter.false); + + if (opcodenum === Opcode.OP_CHECKMULTISIGVERIFY) { + if (fSuccess) { + this.stack.pop(); + } else { + this.errstr = 'SCRIPT_ERR_CHECKMULTISIGVERIFY'; + return false; + } + } + } + break; + + default: + this.errstr = 'SCRIPT_ERR_BAD_OPCODE'; + return false; + } + } + + return true; +}; + diff --git a/src/backend/wallet/lib/script/script.js b/src/backend/wallet/lib/script/script.js new file mode 100644 index 0000000..24be500 --- /dev/null +++ b/src/backend/wallet/lib/script/script.js @@ -0,0 +1,1163 @@ +'use strict'; + +import Address from '../address'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import Hash from '../crypto/hash'; +import Opcode from '../opcode'; +import PublicKey from '../publickey'; +import Signature from '../crypto/signature'; +import Networks from '../networks'; +import $ from '../util/preconditions'; +import _ from 'lodash'; +import errors from '../errors'; +import buffer from 'buffer'; +import BufferUtil from '../util/buffer'; +import JSUtil from '../util/js'; + +/** + * A bitcoin transaction script. Each transaction's inputs and outputs + * has a script that is evaluated to validate it's spending. + * + * See https://en.bitcoin.it/wiki/Script + * + * @constructor + * @param {Object|string|Buffer=} from optional data to populate script + */ +var Script = function Script(from) { + if (!(this instanceof Script)) { + return new Script(from); + } + this.chunks = []; + + if (BufferUtil.isBuffer(from)) { + return Script.fromBuffer(from); + } else if (from instanceof Address) { + return Script.fromAddress(from); + } else if (from instanceof Script) { + return Script.fromBuffer(from.toBuffer()); + } else if (_.isString(from)) { + return Script.fromString(from); + } else if (_.isObject(from) && _.isArray(from.chunks)) { + this.set(from); + } +}; + +Script.prototype.set = function (obj) { + $.checkArgument(_.isObject(obj)); + $.checkArgument(_.isArray(obj.chunks)); + this.chunks = obj.chunks; + return this; +}; + +Script.fromBuffer = function (buffer) { + var script = new Script(); + script.chunks = []; + + var br = new BufferReader(buffer); + while (!br.finished()) { + try { + var opcodenum = br.readUInt8(); + + var len, buf; + if (opcodenum > 0 && opcodenum < Opcode.OP_PUSHDATA1) { + len = opcodenum; + script.chunks.push({ + buf: br.read(len), + len: len, + opcodenum: opcodenum + }); + } else if (opcodenum === Opcode.OP_PUSHDATA1) { + len = br.readUInt8(); + buf = br.read(len); + script.chunks.push({ + buf: buf, + len: len, + opcodenum: opcodenum + }); + } else if (opcodenum === Opcode.OP_PUSHDATA2) { + len = br.readUInt16LE(); + buf = br.read(len); + script.chunks.push({ + buf: buf, + len: len, + opcodenum: opcodenum + }); + } else if (opcodenum === Opcode.OP_PUSHDATA4) { + len = br.readUInt32LE(); + buf = br.read(len); + script.chunks.push({ + buf: buf, + len: len, + opcodenum: opcodenum + }); + } else { + script.chunks.push({ + opcodenum: opcodenum + }); + } + } catch (e) { + if (e instanceof RangeError) { + throw new errors.Script.InvalidBuffer(buffer.toString('hex')); + } + throw e; + } + } + + return script; +}; + +Script.prototype.toBuffer = function () { + var bw = new BufferWriter(); + + for (var i = 0; i < this.chunks.length; i++) { + var chunk = this.chunks[i]; + var opcodenum = chunk.opcodenum; + bw.writeUInt8(chunk.opcodenum); + if (chunk.buf) { + if (opcodenum < Opcode.OP_PUSHDATA1) { + bw.write(chunk.buf); + } else if (opcodenum === Opcode.OP_PUSHDATA1) { + bw.writeUInt8(chunk.len); + bw.write(chunk.buf); + } else if (opcodenum === Opcode.OP_PUSHDATA2) { + bw.writeUInt16LE(chunk.len); + bw.write(chunk.buf); + } else if (opcodenum === Opcode.OP_PUSHDATA4) { + bw.writeUInt32LE(chunk.len); + bw.write(chunk.buf); + } + } + } + + return bw.concat(); +}; + +Script.fromASM = function (str) { + var script = new Script(); + script.chunks = []; + + var tokens = str.split(' '); + var i = 0; + while (i < tokens.length) { + var token = tokens[i]; + var opcode = Opcode(token); + var opcodenum = opcode.toNumber(); + + if (_.isUndefined(opcodenum)) { + var buf = Buffer.from(tokens[i], 'hex'); + script.chunks.push({ + buf: buf, + len: buf.length, + opcodenum: buf.length + }); + i = i + 1; + } else if (opcodenum === Opcode.OP_PUSHDATA1 || + opcodenum === Opcode.OP_PUSHDATA2 || + opcodenum === Opcode.OP_PUSHDATA4) { + script.chunks.push({ + buf: Buffer.from(tokens[i + 2], 'hex'), + len: parseInt(tokens[i + 1]), + opcodenum: opcodenum + }); + i = i + 3; + } else { + script.chunks.push({ + opcodenum: opcodenum + }); + i = i + 1; + } + } + return script; +}; + +Script.fromHex = function (str) { + return new Script(new buffer.Buffer(str, 'hex')); +}; + +Script.fromString = function (str) { + if (JSUtil.isHexa(str) || str.length === 0) { + return new Script(new buffer.Buffer(str, 'hex')); + } + var script = new Script(); + script.chunks = []; + + var tokens = str.split(' '); + var i = 0; + while (i < tokens.length) { + var token = tokens[i]; + var opcode = Opcode(token); + var opcodenum = opcode.toNumber(); + + if (_.isUndefined(opcodenum)) { + opcodenum = parseInt(token); + if (opcodenum > 0 && opcodenum < Opcode.OP_PUSHDATA1) { + script.chunks.push({ + buf: Buffer.from(tokens[i + 1].slice(2), 'hex'), + len: opcodenum, + opcodenum: opcodenum + }); + i = i + 2; + } else { + throw new Error('Invalid script: ' + JSON.stringify(str)); + } + } else if (opcodenum === Opcode.OP_PUSHDATA1 || + opcodenum === Opcode.OP_PUSHDATA2 || + opcodenum === Opcode.OP_PUSHDATA4) { + if (tokens[i + 2].slice(0, 2) !== '0x') { + throw new Error('Pushdata data must start with 0x'); + } + script.chunks.push({ + buf: Buffer.from(tokens[i + 2].slice(2), 'hex'), + len: parseInt(tokens[i + 1]), + opcodenum: opcodenum + }); + i = i + 3; + } else { + script.chunks.push({ + opcodenum: opcodenum + }); + i = i + 1; + } + } + return script; +}; + +Script.prototype._chunkToString = function (chunk, type) { + var opcodenum = chunk.opcodenum; + var asm = (type === 'asm'); + var str = ''; + if (!chunk.buf) { + // no data chunk + if (typeof Opcode.reverseMap[opcodenum] !== 'undefined') { + if (asm) { + // A few cases where the opcode name differs from reverseMap + // aside from 1 to 16 data pushes. + if (opcodenum === 0) { + // OP_0 -> 0 + str = str + ' 0'; + } else if (opcodenum === 79) { + // OP_1NEGATE -> 1 + str = str + ' -1'; + } else { + str = str + ' ' + Opcode(opcodenum).toString(); + } + } else { + str = str + ' ' + Opcode(opcodenum).toString(); + } + } else { + var numstr = opcodenum.toString(16); + if (numstr.length % 2 !== 0) { + numstr = '0' + numstr; + } + if (asm) { + str = str + ' ' + numstr; + } else { + str = str + ' ' + '0x' + numstr; + } + } + } else { + // data chunk + if (!asm && opcodenum === Opcode.OP_PUSHDATA1 || + opcodenum === Opcode.OP_PUSHDATA2 || + opcodenum === Opcode.OP_PUSHDATA4) { + str = str + ' ' + Opcode(opcodenum).toString(); + } + if (chunk.len > 0) { + if (asm) { + str = str + ' ' + chunk.buf.toString('hex'); + } else { + str = str + ' ' + chunk.len + ' ' + '0x' + chunk.buf.toString('hex'); + } + } + } + return str; +}; + +Script.prototype.toASM = function () { + var str = ''; + for (var i = 0; i < this.chunks.length; i++) { + var chunk = this.chunks[i]; + str += this._chunkToString(chunk, 'asm'); + } + + return str.substr(1); +}; + +Script.prototype.toString = function () { + var str = ''; + for (var i = 0; i < this.chunks.length; i++) { + var chunk = this.chunks[i]; + str += this._chunkToString(chunk); + } + + return str.substr(1); +}; + +Script.prototype.toHex = function () { + return this.toBuffer().toString('hex'); +}; + +Script.prototype.inspect = function () { + return ''; +}; + +// script classification methods + +/** + * @returns {boolean} if this is a pay to pubkey hash output script + */ +Script.prototype.isPublicKeyHashOut = function () { + return !!(this.chunks.length === 5 && + this.chunks[0].opcodenum === Opcode.OP_DUP && + this.chunks[1].opcodenum === Opcode.OP_HASH160 && + this.chunks[2].buf && + this.chunks[2].buf.length === 20 && + this.chunks[3].opcodenum === Opcode.OP_EQUALVERIFY && + this.chunks[4].opcodenum === Opcode.OP_CHECKSIG); +}; + +/** + * @returns {boolean} if this is a pay to public key hash input script + */ +Script.prototype.isPublicKeyHashIn = function () { + if (this.chunks.length === 2) { + var signatureBuf = this.chunks[0].buf; + var pubkeyBuf = this.chunks[1].buf; + if (signatureBuf && + signatureBuf.length && + signatureBuf[0] === 0x30 && + pubkeyBuf && + pubkeyBuf.length + ) { + var version = pubkeyBuf[0]; + if ((version === 0x04 || + version === 0x06 || + version === 0x07) && pubkeyBuf.length === 65) { + return true; + } else if ((version === 0x03 || version === 0x02) && pubkeyBuf.length === 33) { + return true; + } + } + } + return false; +}; + +Script.prototype.getPublicKey = function () { + $.checkState(this.isPublicKeyOut(), 'Can\'t retrieve PublicKey from a non-PK output'); + return this.chunks[0].buf; +}; + +Script.prototype.getPublicKeyHash = function () { + $.checkState(this.isPublicKeyHashOut(), 'Can\'t retrieve PublicKeyHash from a non-PKH output'); + return this.chunks[2].buf; +}; + +/** + * @returns {boolean} if this is a public key output script + */ +Script.prototype.isPublicKeyOut = function () { + if (this.chunks.length === 2 && + this.chunks[0].buf && + this.chunks[0].buf.length && + this.chunks[1].opcodenum === Opcode.OP_CHECKSIG) { + var pubkeyBuf = this.chunks[0].buf; + var version = pubkeyBuf[0]; + var isVersion = false; + if ((version === 0x04 || + version === 0x06 || + version === 0x07) && pubkeyBuf.length === 65) { + isVersion = true; + } else if ((version === 0x03 || version === 0x02) && pubkeyBuf.length === 33) { + isVersion = true; + } + if (isVersion) { + return PublicKey.isValid(pubkeyBuf); + } + } + return false; +}; + +/** + * @returns {boolean} if this is a pay to public key input script + */ +Script.prototype.isPublicKeyIn = function () { + if (this.chunks.length === 1) { + var signatureBuf = this.chunks[0].buf; + if (signatureBuf && + signatureBuf.length && + signatureBuf[0] === 0x30) { + return true; + } + } + return false; +}; + +/** + * @returns {boolean} if this is a p2sh output script + */ +Script.prototype.isScriptHashOut = function () { + var buf = this.toBuffer(); + return (buf.length === 23 && + buf[0] === Opcode.OP_HASH160 && + buf[1] === 0x14 && + buf[buf.length - 1] === Opcode.OP_EQUAL); +}; + +/** + * @returns {boolean} if this is a p2wsh output script + */ +Script.prototype.isWitnessScriptHashOut = function () { + var buf = this.toBuffer(); + return (buf.length === 34 && buf[0] === 0 && buf[1] === 32); +}; + +/** + * @returns {boolean} if this is a p2wpkh output script + */ +Script.prototype.isWitnessPublicKeyHashOut = function () { + var buf = this.toBuffer(); + return (buf.length === 22 && buf[0] === 0 && buf[1] === 20); +}; + +/** + * @param {Object=} values - The return values + * @param {Number} values.version - Set with the witness version + * @param {Buffer} values.program - Set with the witness program + * @returns {boolean} if this is a p2wpkh output script + */ +Script.prototype.isWitnessProgram = function (values) { + if (!values) { + values = {}; + } + var buf = this.toBuffer(); + if (buf.length < 4 || buf.length > 42) { + return false; + } + if (buf[0] !== Opcode.OP_0 && !(buf[0] >= Opcode.OP_1 && buf[0] <= Opcode.OP_16)) { + return false; + } + + if (buf.length === buf[1] + 2) { + values.version = buf[0]; + values.program = buf.slice(2, buf.length); + return true; + } + + return false; +}; + +/** + * @returns {boolean} if this is a p2sh input script + * Note that these are frequently indistinguishable from pubkeyhashin + */ +Script.prototype.isScriptHashIn = function () { + if (this.chunks.length <= 1) { + return false; + } + var redeemChunk = this.chunks[this.chunks.length - 1]; + var redeemBuf = redeemChunk.buf; + if (!redeemBuf) { + return false; + } + + var redeemScript; + try { + redeemScript = Script.fromBuffer(redeemBuf); + } catch (e) { + if (e instanceof errors.Script.InvalidBuffer) { + return false; + } + throw e; + } + var type = redeemScript.classify(); + return type !== Script.types.UNKNOWN; +}; + +/** + * @returns {boolean} if this is a mutlsig output script + */ +Script.prototype.isMultisigOut = function () { + return (this.chunks.length > 3 && + Opcode.isSmallIntOp(this.chunks[0].opcodenum) && + this.chunks.slice(1, this.chunks.length - 2).every(function (obj) { + return obj.buf && BufferUtil.isBuffer(obj.buf); + }) && + Opcode.isSmallIntOp(this.chunks[this.chunks.length - 2].opcodenum) && + this.chunks[this.chunks.length - 1].opcodenum === Opcode.OP_CHECKMULTISIG); +}; + + +/** + * @returns {boolean} if this is a multisig input script + */ +Script.prototype.isMultisigIn = function () { + return this.chunks.length >= 2 && + this.chunks[0].opcodenum === 0 && + this.chunks.slice(1, this.chunks.length).every(function (obj) { + return obj.buf && + BufferUtil.isBuffer(obj.buf) && + Signature.isTxDER(obj.buf); + }); +}; + +/** + * @returns {boolean} true if this is a valid standard OP_RETURN output + */ +Script.prototype.isDataOut = function () { + return this.chunks.length >= 1 && + this.chunks[0].opcodenum === Opcode.OP_RETURN && + (this.chunks.length === 1 || + (this.chunks.length === 2 && + this.chunks[1].buf && + this.chunks[1].buf.length <= Script.OP_RETURN_STANDARD_SIZE && + this.chunks[1].length === this.chunks.len)); +}; + +/** + * Retrieve the associated data for this script. + * In the case of a pay to public key hash or P2SH, return the hash. + * In the case of a standard OP_RETURN, return the data + * @returns {Buffer} + */ +Script.prototype.getData = function () { + if (this.isDataOut() || this.isScriptHashOut()) { + if (_.isUndefined(this.chunks[1])) { + return Buffer.alloc(0); + } else { + return Buffer.from(this.chunks[1].buf); + } + } + if (this.isPublicKeyHashOut()) { + return Buffer.from(this.chunks[2].buf); + } + throw new Error('Unrecognized script type to get data from'); +}; + +/** + * @returns {boolean} if the script is only composed of data pushing + * opcodes or small int opcodes (OP_0, OP_1, ..., OP_16) + */ +Script.prototype.isPushOnly = function () { + return _.every(this.chunks, function (chunk) { + return chunk.opcodenum <= Opcode.OP_16; + }); +}; + + +Script.types = {}; +Script.types.UNKNOWN = 'Unknown'; +Script.types.PUBKEY_OUT = 'Pay to public key'; +Script.types.PUBKEY_IN = 'Spend from public key'; +Script.types.PUBKEYHASH_OUT = 'Pay to public key hash'; +Script.types.PUBKEYHASH_IN = 'Spend from public key hash'; +Script.types.SCRIPTHASH_OUT = 'Pay to script hash'; +Script.types.SCRIPTHASH_IN = 'Spend from script hash'; +Script.types.MULTISIG_OUT = 'Pay to multisig'; +Script.types.MULTISIG_IN = 'Spend from multisig'; +Script.types.DATA_OUT = 'Data push'; + +Script.OP_RETURN_STANDARD_SIZE = 80; + +/** + * @returns {object} The Script type if it is a known form, + * or Script.UNKNOWN if it isn't + */ +Script.prototype.classify = function () { + if (this._isInput) { + return this.classifyInput(); + } else if (this._isOutput) { + return this.classifyOutput(); + } else { + var outputType = this.classifyOutput(); + return outputType != Script.types.UNKNOWN ? outputType : this.classifyInput(); + } +}; + +Script.outputIdentifiers = {}; +Script.outputIdentifiers.PUBKEY_OUT = Script.prototype.isPublicKeyOut; +Script.outputIdentifiers.PUBKEYHASH_OUT = Script.prototype.isPublicKeyHashOut; +Script.outputIdentifiers.MULTISIG_OUT = Script.prototype.isMultisigOut; +Script.outputIdentifiers.SCRIPTHASH_OUT = Script.prototype.isScriptHashOut; +Script.outputIdentifiers.DATA_OUT = Script.prototype.isDataOut; + +/** + * @returns {object} The Script type if it is a known form, + * or Script.UNKNOWN if it isn't + */ +Script.prototype.classifyOutput = function () { + for (var type in Script.outputIdentifiers) { + if (Script.outputIdentifiers[type].bind(this)()) { + return Script.types[type]; + } + } + return Script.types.UNKNOWN; +}; + +Script.inputIdentifiers = {}; +Script.inputIdentifiers.PUBKEY_IN = Script.prototype.isPublicKeyIn; +Script.inputIdentifiers.PUBKEYHASH_IN = Script.prototype.isPublicKeyHashIn; +Script.inputIdentifiers.MULTISIG_IN = Script.prototype.isMultisigIn; +Script.inputIdentifiers.SCRIPTHASH_IN = Script.prototype.isScriptHashIn; + +/** + * @returns {object} The Script type if it is a known form, + * or Script.UNKNOWN if it isn't + */ +Script.prototype.classifyInput = function () { + for (var type in Script.inputIdentifiers) { + if (Script.inputIdentifiers[type].bind(this)()) { + return Script.types[type]; + } + } + return Script.types.UNKNOWN; +}; + + +/** + * @returns {boolean} if script is one of the known types + */ +Script.prototype.isStandard = function () { + // TODO: Add BIP62 compliance + return this.classify() !== Script.types.UNKNOWN; +}; + + +// Script construction methods + +/** + * Adds a script element at the start of the script. + * @param {*} obj a string, number, Opcode, Buffer, or object to add + * @returns {Script} this script instance + */ +Script.prototype.prepend = function (obj) { + this._addByType(obj, true); + return this; +}; + +/** + * Compares a script with another script + */ +Script.prototype.equals = function (script) { + $.checkState(script instanceof Script, 'Must provide another script'); + if (this.chunks.length !== script.chunks.length) { + return false; + } + var i; + for (i = 0; i < this.chunks.length; i++) { + if (BufferUtil.isBuffer(this.chunks[i].buf) && !BufferUtil.isBuffer(script.chunks[i].buf)) { + return false; + } + if (BufferUtil.isBuffer(this.chunks[i].buf) && !BufferUtil.equals(this.chunks[i].buf, script.chunks[i].buf)) { + return false; + } else if (this.chunks[i].opcodenum !== script.chunks[i].opcodenum) { + return false; + } + } + return true; +}; + +/** + * Adds a script element to the end of the script. + * + * @param {*} obj a string, number, Opcode, Buffer, or object to add + * @returns {Script} this script instance + * + */ +Script.prototype.add = function (obj) { + this._addByType(obj, false); + return this; +}; + +Script.prototype._addByType = function (obj, prepend) { + if (typeof obj === 'string') { + this._addOpcode(obj, prepend); + } else if (typeof obj === 'number') { + this._addOpcode(obj, prepend); + } else if (obj instanceof Opcode) { + this._addOpcode(obj, prepend); + } else if (BufferUtil.isBuffer(obj)) { + this._addBuffer(obj, prepend); + } else if (obj instanceof Script) { + this.chunks = this.chunks.concat(obj.chunks); + } else if (typeof obj === 'object') { + this._insertAtPosition(obj, prepend); + } else { + throw new Error('Invalid script chunk'); + } +}; + +Script.prototype._insertAtPosition = function (op, prepend) { + if (prepend) { + this.chunks.unshift(op); + } else { + this.chunks.push(op); + } +}; + +Script.prototype._addOpcode = function (opcode, prepend) { + var op; + if (typeof opcode === 'number') { + op = opcode; + } else if (opcode instanceof Opcode) { + op = opcode.toNumber(); + } else { + op = Opcode(opcode).toNumber(); + } + this._insertAtPosition({ + opcodenum: op + }, prepend); + return this; +}; + +Script.prototype._addBuffer = function (buf, prepend) { + var opcodenum; + var len = buf.length; + if (len >= 0 && len < Opcode.OP_PUSHDATA1) { + opcodenum = len; + } else if (len < Math.pow(2, 8)) { + opcodenum = Opcode.OP_PUSHDATA1; + } else if (len < Math.pow(2, 16)) { + opcodenum = Opcode.OP_PUSHDATA2; + } else if (len < Math.pow(2, 32)) { + opcodenum = Opcode.OP_PUSHDATA4; + } else { + throw new Error('You can\'t push that much data'); + } + this._insertAtPosition({ + buf: buf, + len: len, + opcodenum: opcodenum + }, prepend); + return this; +}; + +Script.prototype.hasCodeseparators = function () { + for (var i = 0; i < this.chunks.length; i++) { + if (this.chunks[i].opcodenum === Opcode.OP_CODESEPARATOR) { + return true; + } + } + return false; +}; + +Script.prototype.removeCodeseparators = function () { + var chunks = []; + for (var i = 0; i < this.chunks.length; i++) { + if (this.chunks[i].opcodenum !== Opcode.OP_CODESEPARATOR) { + chunks.push(this.chunks[i]); + } + } + this.chunks = chunks; + return this; +}; + +// high level script builder methods + +/** + * @returns {Script} a new Multisig output script for given public keys, + * requiring m of those public keys to spend + * @param {PublicKey[]} publicKeys - list of all public keys controlling the output + * @param {number} threshold - amount of required signatures to spend the output + * @param {Object=} opts - Several options: + * - noSorting: defaults to false, if true, don't sort the given + * public keys before creating the script + */ +Script.buildMultisigOut = function (publicKeys, threshold, opts) { + $.checkArgument(threshold <= publicKeys.length, + 'Number of required signatures must be less than or equal to the number of public keys'); + opts = opts || {}; + var script = new Script(); + script.add(Opcode.smallInt(threshold)); + publicKeys = _.map(publicKeys, PublicKey); + var sorted = publicKeys; + if (!opts.noSorting) { + sorted = _.sortBy(publicKeys, function (publicKey) { + return publicKey.toString('hex'); + }); + } + for (var i = 0; i < sorted.length; i++) { + var publicKey = sorted[i]; + script.add(publicKey.toBuffer()); + } + script.add(Opcode.smallInt(publicKeys.length)); + script.add(Opcode.OP_CHECKMULTISIG); + return script; +}; + +Script.buildWitnessMultisigOutFromScript = function (script) { + if (script instanceof Script) { + var s = new Script(); + s.add(Opcode.OP_0); + s.add(Hash.sha256(script.toBuffer())); + return s; + } else { + throw new TypeError('First argument is expected to be a p2sh script'); + } +}; + +/** + * A new Multisig input script for the given public keys, requiring m of those public keys to spend + * + * @param {PublicKey[]} pubkeys list of all public keys controlling the output + * @param {number} threshold amount of required signatures to spend the output + * @param {Array} signatures and array of signature buffers to append to the script + * @param {Object=} opts + * @param {boolean=} opts.noSorting don't sort the given public keys before creating the script (false by default) + * @param {Script=} opts.cachedMultisig don't recalculate the redeemScript + * + * @returns {Script} + */ +Script.buildMultisigIn = function (pubkeys, threshold, signatures, opts) { + $.checkArgument(_.isArray(pubkeys)); + $.checkArgument(_.isNumber(threshold)); + $.checkArgument(_.isArray(signatures)); + opts = opts || {}; + var s = new Script(); + s.add(Opcode.OP_0); + _.each(signatures, function (signature) { + $.checkArgument(BufferUtil.isBuffer(signature), 'Signatures must be an array of Buffers'); + // TODO: allow signatures to be an array of Signature objects + s.add(signature); + }); + return s; +}; + +/** + * A new P2SH Multisig input script for the given public keys, requiring m of those public keys to spend + * + * @param {PublicKey[]} pubkeys list of all public keys controlling the output + * @param {number} threshold amount of required signatures to spend the output + * @param {Array} signatures and array of signature buffers to append to the script + * @param {Object=} opts + * @param {boolean=} opts.noSorting don't sort the given public keys before creating the script (false by default) + * @param {Script=} opts.cachedMultisig don't recalculate the redeemScript + * + * @returns {Script} + */ +Script.buildP2SHMultisigIn = function (pubkeys, threshold, signatures, opts) { + $.checkArgument(_.isArray(pubkeys)); + $.checkArgument(_.isNumber(threshold)); + $.checkArgument(_.isArray(signatures)); + opts = opts || {}; + var s = new Script(); + s.add(Opcode.OP_0); + _.each(signatures, function (signature) { + $.checkArgument(BufferUtil.isBuffer(signature), 'Signatures must be an array of Buffers'); + // TODO: allow signatures to be an array of Signature objects + s.add(signature); + }); + s.add((opts.cachedMultisig || Script.buildMultisigOut(pubkeys, threshold, opts)).toBuffer()); + return s; +}; + +/** + * @returns {Script} a new pay to public key hash output for the given + * address or public key + * @param {(Address|PublicKey)} to - destination address or public key + */ +Script.buildPublicKeyHashOut = function (to) { + $.checkArgument(!_.isUndefined(to)); + $.checkArgument(to instanceof PublicKey || to instanceof Address || _.isString(to)); + if (to instanceof PublicKey) { + to = to.toAddress(); + } else if (_.isString(to)) { + to = new Address(to); + } + var s = new Script(); + s.add(Opcode.OP_DUP) + .add(Opcode.OP_HASH160) + .add(to.hashBuffer) + .add(Opcode.OP_EQUALVERIFY) + .add(Opcode.OP_CHECKSIG); + s._network = to.network; + return s; +}; + +/** + * @returns {Script} a new pay to public key output for the given + * public key + */ +Script.buildPublicKeyOut = function (pubkey) { + $.checkArgument(pubkey instanceof PublicKey); + var s = new Script(); + s.add(pubkey.toBuffer()) + .add(Opcode.OP_CHECKSIG); + return s; +}; + +/** + * @returns {Script} a new OP_RETURN script with data + * @param {(string|Buffer)} data - the data to embed in the output + * @param {(string)} encoding - the type of encoding of the string + */ +Script.buildDataOut = function (data, encoding) { + $.checkArgument(_.isUndefined(data) || _.isString(data) || BufferUtil.isBuffer(data)); + if (_.isString(data)) { + data = Buffer.from(data, encoding); + } + var s = new Script(); + s.add(Opcode.OP_RETURN); + if (!_.isUndefined(data)) { + s.add(data); + } + return s; +}; + +/** + * @param {Script|Address} script - the redeemScript for the new p2sh output. + * It can also be a p2sh address + * @returns {Script} new pay to script hash script for given script + */ +Script.buildScriptHashOut = function (script) { + $.checkArgument(script instanceof Script || + (script instanceof Address && script.isPayToScriptHash())); + var s = new Script(); + s.add(Opcode.OP_HASH160) + .add(script instanceof Address ? script.hashBuffer : Hash.sha256ripemd160(script.toBuffer())) + .add(Opcode.OP_EQUAL); + + s._network = script._network || script.network; + return s; +}; + +/** + * Builds a scriptSig (a script for an input) that signs a public key output script. + * + * @param {Signature|Buffer} signature - a Signature object, or the signature in DER canonical encoding + * @param {number=} sigtype - the type of the signature (defaults to SIGHASH_ALL) + */ +Script.buildPublicKeyIn = function (signature, sigtype) { + $.checkArgument(signature instanceof Signature || BufferUtil.isBuffer(signature)); + $.checkArgument(_.isUndefined(sigtype) || _.isNumber(sigtype)); + if (signature instanceof Signature) { + signature = signature.toBuffer(); + } + var script = new Script(); + script.add(BufferUtil.concat([ + signature, + BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL) + ])); + return script; +}; + +/** + * Builds a scriptSig (a script for an input) that signs a public key hash + * output script. + * + * @param {Buffer|string|PublicKey} publicKey + * @param {Signature|Buffer} signature - a Signature object, or the signature in DER canonical encoding + * @param {number=} sigtype - the type of the signature (defaults to SIGHASH_ALL) + */ +Script.buildPublicKeyHashIn = function (publicKey, signature, sigtype) { + $.checkArgument(signature instanceof Signature || BufferUtil.isBuffer(signature)); + $.checkArgument(_.isUndefined(sigtype) || _.isNumber(sigtype)); + if (signature instanceof Signature) { + signature = signature.toBuffer(); + } + var script = new Script() + .add(BufferUtil.concat([ + signature, + BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL) + ])) + .add(new PublicKey(publicKey).toBuffer()); + return script; +}; + +/** + * @returns {Script} an empty script + */ +Script.empty = function () { + return new Script(); +}; + +/** + * @returns {Script} a new pay to script hash script that pays to this script + */ +Script.prototype.toScriptHashOut = function () { + return Script.buildScriptHashOut(this); +}; + +/** + * @return {Script} an output script built from the address + */ +Script.fromAddress = function (address) { + address = Address(address); + if (address.isPayToScriptHash()) { + return Script.buildScriptHashOut(address); + } else if (address.isPayToPublicKeyHash()) { + return Script.buildPublicKeyHashOut(address); + } + throw new errors.Script.UnrecognizedAddress(address); +}; + +/** + * Will return the associated address information object + * @return {Address|boolean} + */ +Script.prototype.getAddressInfo = function (opts) { + if (this._isInput) { + return this._getInputAddressInfo(); + } else if (this._isOutput) { + return this._getOutputAddressInfo(); + } else { + var info = this._getOutputAddressInfo(); + if (!info) { + return this._getInputAddressInfo(); + } + return info; + } +}; + +/** + * Will return the associated output scriptPubKey address information object + * @return {Address|boolean} + * @private + */ +Script.prototype._getOutputAddressInfo = function () { + var info = {}; + if (this.isScriptHashOut()) { + info.hashBuffer = this.getData(); + info.type = Address.PayToScriptHash; + } else if (this.isPublicKeyHashOut()) { + info.hashBuffer = this.getData(); + info.type = Address.PayToPublicKeyHash; + } else { + return false; + } + return info; +}; + +/** + * Will return the associated input scriptSig address information object + * @return {Address|boolean} + * @private + */ +Script.prototype._getInputAddressInfo = function () { + var info = {}; + if (this.isPublicKeyHashIn()) { + // hash the publickey found in the scriptSig + info.hashBuffer = Hash.sha256ripemd160(this.chunks[1].buf); + info.type = Address.PayToPublicKeyHash; + } else if (this.isScriptHashIn()) { + // hash the redeemscript found at the end of the scriptSig + info.hashBuffer = Hash.sha256ripemd160(this.chunks[this.chunks.length - 1].buf); + info.type = Address.PayToScriptHash; + } else { + return false; + } + return info; +}; + +/** + * @param {Network=} network + * @return {Address|boolean} the associated address for this script if possible, or false + */ +Script.prototype.toAddress = function (network) { + var info = this.getAddressInfo(); + if (!info) { + return false; + } + info.network = Networks.get(network) || this._network || Networks.defaultNetwork; + return new Address(info); +}; + +/** + * Analogous to bitcoind's FindAndDelete. Find and delete equivalent chunks, + * typically used with push data chunks. Note that this will find and delete + * not just the same data, but the same data with the same push data op as + * produced by default. i.e., if a pushdata in a tx does not use the minimal + * pushdata op, then when you try to remove the data it is pushing, it will not + * be removed, because they do not use the same pushdata op. + */ +Script.prototype.findAndDelete = function (script) { + var buf = script.toBuffer(); + var hex = buf.toString('hex'); + for (var i = 0; i < this.chunks.length; i++) { + var script2 = Script({ + chunks: [this.chunks[i]] + }); + var buf2 = script2.toBuffer(); + var hex2 = buf2.toString('hex'); + if (hex === hex2) { + this.chunks.splice(i, 1); + } + } + return this; +}; + +/** + * Comes from bitcoind's script interpreter CheckMinimalPush function + * @returns {boolean} if the chunk {i} is the smallest way to push that particular data. + */ +Script.prototype.checkMinimalPush = function (i) { + var chunk = this.chunks[i]; + var buf = chunk.buf; + var opcodenum = chunk.opcodenum; + if (!buf) { + return true; + } + if (buf.length === 0) { + // Could have used OP_0. + return opcodenum === Opcode.OP_0; + } else if (buf.length === 1 && buf[0] >= 1 && buf[0] <= 16) { + // Could have used OP_1 .. OP_16. + return opcodenum === Opcode.OP_1 + (buf[0] - 1); + } else if (buf.length === 1 && buf[0] === 0x81) { + // Could have used OP_1NEGATE + return opcodenum === Opcode.OP_1NEGATE; + } else if (buf.length <= 75) { + // Could have used a direct push (opcode indicating number of bytes pushed + those bytes). + return opcodenum === buf.length; + } else if (buf.length <= 255) { + // Could have used OP_PUSHDATA. + return opcodenum === Opcode.OP_PUSHDATA1; + } else if (buf.length <= 65535) { + // Could have used OP_PUSHDATA2. + return opcodenum === Opcode.OP_PUSHDATA2; + } + return true; +}; + +/** + * Comes from bitcoind's script DecodeOP_N function + * @param {number} opcode + * @returns {number} numeric value in range of 0 to 16 + */ +Script.prototype._decodeOP_N = function (opcode) { + if (opcode === Opcode.OP_0) { + return 0; + } else if (opcode >= Opcode.OP_1 && opcode <= Opcode.OP_16) { + return opcode - (Opcode.OP_1 - 1); + } else { + throw new Error('Invalid opcode: ' + JSON.stringify(opcode)); + } +}; + +/** + * Comes from bitcoind's script GetSigOpCount(boolean) function + * @param {boolean} use current (true) or pre-version-0.6 (false) logic + * @returns {number} number of signature operations required by this script + */ +Script.prototype.getSignatureOperationsCount = function (accurate) { + accurate = (_.isUndefined(accurate) ? true : accurate); + var self = this; + var n = 0; + var lastOpcode = Opcode.OP_INVALIDOPCODE; + _.each(self.chunks, function getChunk(chunk) { + var opcode = chunk.opcodenum; + if (opcode == Opcode.OP_CHECKSIG || opcode == Opcode.OP_CHECKSIGVERIFY) { + n++; + } else if (opcode == Opcode.OP_CHECKMULTISIG || opcode == Opcode.OP_CHECKMULTISIGVERIFY) { + if (accurate && lastOpcode >= Opcode.OP_1 && lastOpcode <= Opcode.OP_16) { + n += self._decodeOP_N(lastOpcode); + } else { + n += 20; + } + } + lastOpcode = opcode; + }); + return n; +}; + +export default Script; diff --git a/src/backend/wallet/lib/transaction/assetcreatetx.js b/src/backend/wallet/lib/transaction/assetcreatetx.js new file mode 100644 index 0000000..52859e8 --- /dev/null +++ b/src/backend/wallet/lib/transaction/assetcreatetx.js @@ -0,0 +1,118 @@ +'use strict'; + +import _ from 'lodash'; +import assert from "assert"; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import WriterHelper from '../util/writerhelper'; +import errors from '../errors'; + +var AssetCreateTx = function AssetCreateTx(arg) { + if (!(this instanceof AssetCreateTx)) { + return new AssetCreateTx(arg); + } + var info = AssetCreateTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.srcRegId = info.srcRegId; + this.fees = info.fees; + this.feesCoinSymbol = info.feesCoinSymbol; + this.assetData = info.assetData; + + return this; +}; + +AssetCreateTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = AssetCreateTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for ContractTx'); + } + return info; +}; + +AssetCreateTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + // if (!Util.checkRegId(data.srcRegId)) { + // throw new errors.InvalidArgument("srcRegId", "Invalid reg id"); + // } + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + srcRegId: data.srcRegId, + fees: data.fees, + feesCoinSymbol: data.feesCoinSymbol, + assetData: data.assetData, + }; + return info; +}; + +AssetCreateTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarInt(4, this.nVersion) + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else { + return false; + } + writer.writeString(Buffer.from(this.feesCoinSymbol)) + writer.writeVarInt(8, this.fees) + + var reg = /^[A-Z]{6,7}$/; + if (!reg.test(this.assetData.assetSymbol)) + return false; + writer.writeAssetData(this.assetData, this.network) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +AssetCreateTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +AssetCreateTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nVersion) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else { + return false; + } + writer.writeString(Buffer.from(this.feesCoinSymbol)) + writer.writeVarInt(8, this.fees) + var reg = /^[A-Z]{6,7}$/; + if (!reg.test(this.assetData.assetSymbol)) + return false + writer.writeAssetData(this.assetData, this.network) + + var sigBuf = this._Signtx(privateKey) + writer.writeBuf(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default AssetCreateTx; diff --git a/src/backend/wallet/lib/transaction/assetupdatetx.js b/src/backend/wallet/lib/transaction/assetupdatetx.js new file mode 100644 index 0000000..3a7419f --- /dev/null +++ b/src/backend/wallet/lib/transaction/assetupdatetx.js @@ -0,0 +1,123 @@ +'use strict'; + +import _ from 'lodash'; +import assert from "assert"; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import WriterHelper from '../util/writerhelper'; +import errors from '../errors'; + +var AssetUpdateTx = function AssetUpdateTx(arg) { + if (!(this instanceof AssetUpdateTx)) { + return new AssetUpdateTx(arg); + } + var info = AssetUpdateTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.srcRegId = info.srcRegId; + this.destRegId = info.destRegId; + this.fees = info.fees; + this.feesCoinSymbol = info.feesCoinSymbol; + this.assetUpdateData = info.assetUpdateData; + this.assetSymbol = info.assetSymbol; + + return this; +}; + +AssetUpdateTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = AssetUpdateTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for ContractTx'); + } + return info; +}; + +AssetUpdateTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + if (!Util.checkRegId(data.srcRegId)) { + throw new errors.InvalidArgument("srcRegId", "Invalid reg id"); + } + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + srcRegId: data.srcRegId, + destRegId: data.destRegId, + fees: data.fees, + feesCoinSymbol: data.feesCoinSymbol, + assetUpdateData: data.assetUpdateData, + assetSymbol: data.assetSymbol, + }; + return info; +}; + +AssetUpdateTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarInt(4, this.nVersion) + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else { + return false; + } + writer.writeString(Buffer.from(this.feesCoinSymbol)) + writer.writeVarInt(8, this.fees) + var reg = /^[A-Z]{6,7}$/; + if (!reg.test(this.assetSymbol)) + return false + writer.writeString(Buffer.from(this.assetSymbol)) + writer.writeassetUpdateData(this.assetUpdateData) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +AssetUpdateTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +AssetUpdateTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nVersion) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else { + return false; + } + writer.writeString(Buffer.from(this.feesCoinSymbol)) + writer.writeVarInt(8, this.fees) + var reg = /^[A-Z]{6,7}$/; + if (!reg.test(this.assetSymbol)) + return false + writer.writeString(Buffer.from(this.assetSymbol)) + writer.writeassetUpdateData(this.assetUpdateData) + + var sigBuf = this._Signtx(privateKey) + writer.writeBuf(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default AssetUpdateTx; diff --git a/src/backend/wallet/lib/transaction/cdpliquidatetx.js b/src/backend/wallet/lib/transaction/cdpliquidatetx.js new file mode 100644 index 0000000..ef089f4 --- /dev/null +++ b/src/backend/wallet/lib/transaction/cdpliquidatetx.js @@ -0,0 +1,175 @@ +'use strict'; +//cdp创建 +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import BufferWriter from '../encoding/bufferwriter'; + +var CdpLiquiDateTx = function CdpLiquiDateTx(arg) { + if (!(this instanceof CdpLiquiDateTx)) { + return new CdpLiquiDateTx(arg); + } + var info = CdpLiquiDateTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.txUid = info.txUid; + this.fees = info.fees; + this.cdpTxId = info.cdpTxId; + this.fee_symbol = info.fee_symbol; + this.publicKey = info.publicKey; + this.scoinsToLiquidate = info.scoinsToLiquidate; + this.assetSymbol = info.assetSymbol; + return this; +}; + +CdpLiquiDateTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = CdpLiquiDateTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +CdpLiquiDateTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + txUid: data.txUid, + fees: data.fees, + cdpTxId: data.cdpTxId, + scoinsToLiquidate: data.scoinsToLiquidate, + publicKey: data.publicKey, + fee_symbol: data.fee_symbol, + assetSymbol: data.assetSymbol, + }; + return info; +}; + +CdpLiquiDateTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.txUid != null && !_.isEmpty(this.txUid)) { + var REGID = Util.splitRegID(this.txUid) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.fee_symbol == null || _.isEmpty(this.fee_symbol)) return fasle; + writer.writeString(this.fee_symbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.cdpTxId == null || this.cdpTxId.length < 64) return false + var cdpTxidBuf = new Buffer.from(this.cdpTxId, 'hex') + writer.writeReverse(cdpTxidBuf) + + writer.writeString(this.assetSymbol) + if (! /^[0-9]*[0-9][0-9]*$/.test(this.scoinsToLiquidate)) + return false; + var scoinsToLiquidateBuf = Util.writeVarInt(8, this.scoinsToLiquidate) + writer.write(scoinsToLiquidateBuf) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + return Hash.sha256sha256(serialBuf); +} + +CdpLiquiDateTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + return sigBuf; +} + +CdpLiquiDateTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + if (this.txUid != null && !_.isEmpty(this.txUid)) { + var REGID = Util.splitRegID(this.txUid) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.fee_symbol == null || _.isEmpty(this.fee_symbol)) return fasle; + writer.writeString(this.fee_symbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.cdpTxId == null || this.cdpTxId.length < 64) return false + var cdpTxidBuf = new Buffer.from(this.cdpTxId, 'hex') + writer.writeReverse(cdpTxidBuf) + + writer.writeString(this.assetSymbol) + + if (! /^[0-9]*[0-9][0-9]*$/.test(this.scoinsToLiquidate)) + return false; + var scoinsToLiquidateBuf = Util.writeVarInt(8, this.scoinsToLiquidate) + writer.write(scoinsToLiquidateBuf) + + var sigBuf = this._Signtx(privateKey) + + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default CdpLiquiDateTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/cdpredeemtx.js b/src/backend/wallet/lib/transaction/cdpredeemtx.js new file mode 100644 index 0000000..20458f5 --- /dev/null +++ b/src/backend/wallet/lib/transaction/cdpredeemtx.js @@ -0,0 +1,178 @@ +'use strict'; +//cdp赎回 +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import BufferWriter from '../encoding/bufferwriter'; +import WriterHelper from '../util/writerhelper'; + +var CdpRedeemTx = function CdpRedeemTx(arg) { + if (!(this instanceof CdpRedeemTx)) { + return new CdpRedeemTx(arg); + } + var info = CdpRedeemTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.txUid = info.txUid; + this.fees = info.fees; + this.cdpTxId = info.cdpTxId; + this.scoins_to_repay = info.scoins_to_repay; + this.assetMap = info.assetMap; + this.fee_symbol = info.fee_symbol; + this.publicKey = info.publicKey; + this.network = info.network; + + return this; +}; + +CdpRedeemTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = CdpRedeemTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +CdpRedeemTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + txUid: data.txUid, + fees: data.fees, + cdpTxId: data.cdpTxId, + scoins_to_repay: data.scoins_to_repay, + assetMap: data.assetMap, + publicKey: data.publicKey, + fee_symbol: data.fee_symbol + }; + return info; +}; + +CdpRedeemTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + if (this.txUid != null && !_.isEmpty(this.txUid)) { + var REGID = Util.splitRegID(this.txUid) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.fee_symbol == null || _.isEmpty(this.fee_symbol)) return fasle; + writer.writeString(this.fee_symbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + if (this.cdpTxId == null) return false + var cdpTxidBuf = new Buffer.from(this.cdpTxId, 'hex') + writer.writeReverse(cdpTxidBuf) + + if (! /^[0-9]*[0-9][0-9]*$/.test(this.scoins_to_repay)) + return false; + var scoins_to_repayBuf = Util.writeVarInt(8, this.scoins_to_repay) + writer.write(scoins_to_repayBuf) + + writer.writeCdpAsset(this.assetMap) + + var serialBuf = writer.toBuffer() + // console.log(serialBuf.toString('hex')) + return Hash.sha256sha256(serialBuf); +} + +CdpRedeemTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +CdpRedeemTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.txUid != null && !_.isEmpty(this.txUid)) { + var REGID = Util.splitRegID(this.txUid) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.fee_symbol == null || _.isEmpty(this.fee_symbol)) return fasle; + writer.writeString(this.fee_symbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.cdpTxId == null) return false + var cdpTxidBuf = new Buffer.from(this.cdpTxId, 'hex') + writer.writeReverse(cdpTxidBuf) + + if (! /^[0-9]*[0-9][0-9]*$/.test(this.scoins_to_repay)) + return false; + var scoins_to_repayBuf = Util.writeVarInt(8, this.scoins_to_repay) + writer.write(scoins_to_repayBuf) + + writer.writeCdpAsset(this.assetMap) + + var sigBuf = this._Signtx(privateKey) + + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default CdpRedeemTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/cdpstaketx.js b/src/backend/wallet/lib/transaction/cdpstaketx.js new file mode 100644 index 0000000..7b7f26a --- /dev/null +++ b/src/backend/wallet/lib/transaction/cdpstaketx.js @@ -0,0 +1,188 @@ +'use strict'; +//cdp创建 +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import BufferWriter from '../encoding/bufferwriter'; +import WriterHelper from '../util/writerhelper'; + +var CdpStakeTx = function CdpStakeTx(arg) { + if (!(this instanceof CdpStakeTx)) { + return new CdpStakeTx(arg); + } + var info = CdpStakeTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.txUid = info.txUid; + this.fees = info.fees; + this.cdpTxId = info.cdpTxId; + this.assetMap = info.assetMap; + this.scoinsToMint = info.scoinsToMint; + this.fee_symbol = info.fee_symbol; + this.scoin_symbol = info.scoin_symbol, + this.publicKey = info.publicKey, + this.network = info.network; + + return this; +}; + +CdpStakeTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = CdpStakeTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +CdpStakeTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + txUid: data.txUid, + fees: data.fees, + cdpTxId: data.cdpTxId, + assetMap: data.assetMap, + scoinsToMint: data.scoinsToMint, + fee_symbol: data.fee_symbol, + scoin_symbol: data.scoin_symbol, + publicKey: data.publicKey, + }; + return info; +}; + +CdpStakeTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + if (this.txUid != null && !_.isEmpty(this.txUid)) { + var REGID = Util.splitRegID(this.txUid) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.fee_symbol == null || _.isEmpty(this.fee_symbol)) return fasle; + writer.writeString(this.fee_symbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.cdpTxId == null) { this.cdpTxId = "0000000000000000000000000000000000000000000000000000000000000000" } + var cdpTxidBuf = new Buffer.from(this.cdpTxId, 'hex') + writer.writeReverse(cdpTxidBuf) + + writer.writeCdpAsset(this.assetMap) + + if (this.scoin_symbol == null || _.isEmpty(this.scoin_symbol)) return fasle; + writer.writeString(this.scoin_symbol) + + if (! /^[0-9]*[0-9][0-9]*$/.test(this.scoinsToMint)) + return false; + var scoinsToMintBuf = Util.writeVarInt(8, this.scoinsToMint) + writer.write(scoinsToMintBuf) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + return Hash.sha256sha256(serialBuf); +} + +CdpStakeTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +CdpStakeTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + if (this.txUid != null && !_.isEmpty(this.txUid)) { + var REGID = Util.splitRegID(this.txUid) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + + if (this.fee_symbol == null || _.isEmpty(this.fee_symbol)) return fasle; + writer.writeString(this.fee_symbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.cdpTxId == null) { this.cdpTxId = "0000000000000000000000000000000000000000000000000000000000000000" } + var cdpTxidBuf = new Buffer.from(this.cdpTxId, 'hex') + writer.writeReverse(cdpTxidBuf) + + writer.writeCdpAsset(this.assetMap) + + if (this.scoin_symbol == null || _.isEmpty(this.scoin_symbol)) return fasle; + writer.writeString(this.scoin_symbol) + + if (! /^[0-9]*[0-9][0-9]*$/.test(this.scoinsToMint)) + return false; + var scoinsToMintBuf = Util.writeVarInt(8, this.scoinsToMint) + writer.write(scoinsToMintBuf) + + + var sigBuf = this._Signtx(privateKey) + + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default CdpStakeTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/commontx.js b/src/backend/wallet/lib/transaction/commontx.js new file mode 100644 index 0000000..b6c33b5 --- /dev/null +++ b/src/backend/wallet/lib/transaction/commontx.js @@ -0,0 +1,181 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; +import Address from '../address' + +var CommonTx = function CommonTx(arg) { + if (!(this instanceof CommonTx)) { + return new CommonTx(arg); + } + var info = CommonTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.srcRegId = info.srcRegId; + this.destAddr = info.destAddr; + this.value = info.value; + this.publicKey = info.publicKey; + this.memo = info.memo; + this.network = info.network; + + return this; +}; + +CommonTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = CommonTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +CommonTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + srcRegId: data.srcRegId, + destAddr: data.destAddr, + value: data.value, + publicKey: data.publicKey, + memo: data.memo + }; + return info; +}; + +CommonTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + var addr = Address.fromString(this.destAddr, this.network, 'pubkeyhash') + //console.log(addr.hashBuffer.toString('hex')) + + var size = addr.hashBuffer.length + writer.writeUInt8(size) + writer.write(addr.hashBuffer) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + var valueBuf = Util.writeVarInt(8, this.value) + writer.write(valueBuf) + + writer.writeString(this.memo) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +CommonTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +CommonTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + var addr = Address.fromString(this.destAddr, this.network, 'pubkeyhash') + //console.log(addr.hashBuffer.toString('hex')) + + var size = addr.hashBuffer.length + writer.writeUInt8(size) + writer.write(addr.hashBuffer) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.value)) + return false; + + var valueBuf = Util.writeVarInt(8, this.value) + writer.write(valueBuf) + + writer.writeString(this.memo) + + var sigBuf = this._Signtx(privateKey) + + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default CommonTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/contracttx.js b/src/backend/wallet/lib/transaction/contracttx.js new file mode 100644 index 0000000..527b4a5 --- /dev/null +++ b/src/backend/wallet/lib/transaction/contracttx.js @@ -0,0 +1,139 @@ +'use strict'; + +import _ from 'lodash'; +import assert from "assert"; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import WriterHelper from '../util/writerhelper'; +import errors from '../errors'; + +var ContractTx = function ContractTx(arg) { + if (!(this instanceof ContractTx)) { + return new ContractTx(arg); + } + var info = ContractTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.srcRegId = info.srcRegId; + this.destRegId = info.destRegId; + this.fees = info.fees; + this.value = info.value; + this.publicKey = info.publicKey; + this.vContract = info.vContract; + + return this; +}; + +ContractTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = ContractTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for ContractTx'); + } + return info; +}; + +ContractTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + // if (!Util.checkRegId(data.srcRegId)) { + // throw new errors.InvalidArgument("srcRegId", "Invalid reg id"); + // } + if (!Util.checkRegId(data.destRegId)) { + throw new errors.InvalidArgument("destRegId", "Invalid reg id"); + } + if (!_.isString(data.vContract) || _.isEmpty(data.vContract)) { + throw new errors.InvalidArgument("vContract", "Invalid vContract"); + } + + var contract = Util.hexToBytes(data.vContract) + if (!contract) { + throw new errors.InvalidArgument("vContract", "Invalid vContract"); + } + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + srcRegId: data.srcRegId, + destRegId: data.destRegId, + fees: data.fees, + value: data.value, + publicKey: data.publicKey, + vContract: contract + }; + return info; +}; + +ContractTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarInt(4, this.nVersion) + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + writer.writeRegId(this.destRegId) + writer.writeVarInt(8, this.fees) + writer.writeVarInt(8, this.value) + writer.writeString(Buffer.from(this.vContract)) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +ContractTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +ContractTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nVersion) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + writer.writeRegId(this.destRegId) + + writer.writeVarInt(8, this.fees) + writer.writeVarInt(8, this.value) + + writer.writeBuf(Buffer.from(this.vContract)) + + var sigBuf = this._Signtx(privateKey) + writer.writeBuf(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default ContractTx; diff --git a/src/backend/wallet/lib/transaction/delegatetx.js b/src/backend/wallet/lib/transaction/delegatetx.js new file mode 100644 index 0000000..cd17066 --- /dev/null +++ b/src/backend/wallet/lib/transaction/delegatetx.js @@ -0,0 +1,149 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import WriterHelper from '../util/writerhelper'; +import errors from '../errors'; + +var DelegateTx = function DelegateTx(arg) { + if (!(this instanceof DelegateTx)) { + return new DelegateTx(arg); + } + var info = DelegateTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.srcRegId = info.srcRegId; + this.publicKey = info.publicKey; + /** + * delegateData array, item is object data of delegate + * [ + * { + * publicKey: string the public key that the votes are received, + * votes: number vote number + * }, + *] + */ + this.delegateData = info.delegateData; + this.fees = info.fees; + + return this; +}; + +DelegateTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = DelegateTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for DelegateTx'); + } + return info; +}; + +DelegateTx._checkDelegateData = function _checkDelegateData(delegateData) { + if (!_.isArray(delegateData) || _.isEmpty(delegateData)) { + throw new errors.InvalidArgument("delegateData", "delegateData must be array and not empty."); + } + + for (var i = 0; i < delegateData.length; i++) { + var publicKey = delegateData[i].publicKey; + + var votes = delegateData[i].votes; + // if(!_.isBuffer(publicKey) || _.isEmpty(publicKey)) { + // throw new errors.InvalidArgument("delegateData", "delegateData[" + i + "].publicKey must be buffer or string, and not empty."); + // } + if (typeof (publicKey) !== 'string' || _.isEmpty(publicKey)) { + throw new errors.InvalidArgument("delegateData", "delegateData[" + i + "].publicKey must be string, and not empty."); + } + if (!_.isNumber(votes)) { + throw new errors.InvalidArgument("delegateData", "delegateData[" + i + "].votes must be number."); + } + } +} + + +DelegateTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + // if (!Util.checkRegId(data.srcRegId)) { + // throw new errors.InvalidArgument("srcRegId", "Invalid reg id"); + // } + DelegateTx._checkDelegateData(data.delegateData) + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + srcRegId: data.srcRegId, + delegateData: data.delegateData, + publicKey: data.publicKey, + fees: data.fees + }; + return info; +}; + + +DelegateTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarInt(4, this.nVersion); + writer.writeUInt8(this.nTxType); + writer.writeVarInt(4, this.nValidHeight); + //writer.writeRegId(this.srcRegId); + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId); + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + writer.writeDelegateData(this.delegateData); + writer.writeVarInt(8, this.fees); + + var serialBuf = writer.toBuffer(); + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +DelegateTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +DelegateTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nVersion) + writer.writeVarInt(4, this.nValidHeight) + // writer.writeRegId(this.srcRegId) + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId); + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + writer.writeDelegateData(this.delegateData) + writer.writeVarInt(8, this.fees) + var sigBuf = this._Signtx(privateKey) + writer.writeBuf(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + +export default DelegateTx; diff --git a/src/backend/wallet/lib/transaction/dexbuylimitordertx.js b/src/backend/wallet/lib/transaction/dexbuylimitordertx.js new file mode 100644 index 0000000..8c53e8a --- /dev/null +++ b/src/backend/wallet/lib/transaction/dexbuylimitordertx.js @@ -0,0 +1,183 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; +import Address from '../address' + +var DexBuyLimitOrderTx = function DexBuyLimitOrderTx(arg) { + if (!(this instanceof DexBuyLimitOrderTx)) { + return new DexBuyLimitOrderTx(arg); + } + var info = DexBuyLimitOrderTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.srcRegId = info.srcRegId; + this.coinSymbol = info.coinSymbol; + this.assetSymbol = info.assetSymbol; + this.assetAmount = info.assetAmount; + this.bidPrice = info.bidPrice; + this.feeSymbol = info.feeSymbol; + this.publicKey = info.publicKey; + this.network = info.network; + + return this; +}; + +DexBuyLimitOrderTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = DexBuyLimitOrderTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +DexBuyLimitOrderTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + feeSymbol: data.feeSymbol, + srcRegId: data.srcRegId, + coinSymbol: data.coinSymbol, + assetSymbol: data.assetSymbol, + assetAmount: data.assetAmount, + publicKey: data.publicKey, + bidPrice: data.bidPrice + }; + return info; +}; + +DexBuyLimitOrderTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var assetAmountBuf = Util.writeVarInt(8, this.assetAmount) + writer.write(assetAmountBuf) + var bidPriceBuf = Util.writeVarInt(8, this.bidPrice) + writer.write(bidPriceBuf) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +DexBuyLimitOrderTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +DexBuyLimitOrderTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var assetAmountBuf = Util.writeVarInt(8, this.assetAmount) + writer.write(assetAmountBuf) + var bidPriceBuf = Util.writeVarInt(8, this.bidPrice) + writer.write(bidPriceBuf) + + var sigBuf = this._Signtx(privateKey) + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default DexBuyLimitOrderTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/dexbuymarketordertx.js b/src/backend/wallet/lib/transaction/dexbuymarketordertx.js new file mode 100644 index 0000000..4486c82 --- /dev/null +++ b/src/backend/wallet/lib/transaction/dexbuymarketordertx.js @@ -0,0 +1,177 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; +import Address from '../address' + +var DexBuyMarketOrderTx = function DexBuyMarketOrderTx(arg) { + if (!(this instanceof DexBuyMarketOrderTx)) { + return new DexBuyMarketOrderTx(arg); + } + var info = DexBuyMarketOrderTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.srcRegId = info.srcRegId; + this.coinSymbol = info.coinSymbol; + this.assetSymbol = info.assetSymbol; + this.coinAmount = info.coinAmount; + this.feeSymbol = info.feeSymbol; + this.publicKey = info.publicKey; + this.network = info.network; + + return this; +}; + +DexBuyMarketOrderTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = DexBuyMarketOrderTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +DexBuyMarketOrderTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + srcRegId: data.srcRegId, + coinSymbol: data.coinSymbol, + assetSymbol: data.assetSymbol, + coinAmount: data.coinAmount, + publicKey: data.publicKey, + feeSymbol: data.feeSymbol + }; + return info; +}; + +DexBuyMarketOrderTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var coinAmountBuf = Util.writeVarInt(8, this.coinAmount) + writer.write(coinAmountBuf) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +DexBuyMarketOrderTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +DexBuyMarketOrderTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var coinAmountBuf = Util.writeVarInt(8, this.coinAmount) + writer.write(coinAmountBuf) + + var sigBuf = this._Signtx(privateKey) + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default DexBuyMarketOrderTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/dexcancelordertx.js b/src/backend/wallet/lib/transaction/dexcancelordertx.js new file mode 100644 index 0000000..0d28c32 --- /dev/null +++ b/src/backend/wallet/lib/transaction/dexcancelordertx.js @@ -0,0 +1,164 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; +import Address from '../address' + +var DexCancelOrderTx = function DexCancelOrderTx(arg) { + if (!(this instanceof DexCancelOrderTx)) { + return new DexCancelOrderTx(arg); + } + var info = DexCancelOrderTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.srcRegId = info.srcRegId; + this.orderId = info.orderId; + this.feeSymbol = info.feeSymbol; + this.publicKey = info.publicKey; + this.network = info.network; + + return this; +}; + +DexCancelOrderTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = DexCancelOrderTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +DexCancelOrderTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + srcRegId: data.srcRegId, + orderId: data.orderId, + publicKey: data.publicKey, + feeSymbol: data.feeSymbol + }; + return info; +}; + +DexCancelOrderTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.orderId == null) return false + var orderIdBuf = new Buffer.from(this.orderId, 'hex'); + writer.writeReverse(orderIdBuf); + + var serialBuf = writer.toBuffer() + // console.log(serialBuf.toString('hex')) + return Hash.sha256sha256(serialBuf); +} + +DexCancelOrderTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +DexCancelOrderTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.orderId == null || this.orderId.length == 0) return false + var orderIdBuf = new Buffer.from(this.orderId, 'hex'); + writer.writeReverse(orderIdBuf); + + + var sigBuf = this._Signtx(privateKey) + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default DexCancelOrderTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/dexselllimitordertx.js b/src/backend/wallet/lib/transaction/dexselllimitordertx.js new file mode 100644 index 0000000..cdd5d33 --- /dev/null +++ b/src/backend/wallet/lib/transaction/dexselllimitordertx.js @@ -0,0 +1,184 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; +import Address from '../address' + +var DexSellLimitOrderTx = function DexSellLimitOrderTx(arg) { + if (!(this instanceof DexSellLimitOrderTx)) { + return new DexSellLimitOrderTx(arg); + } + var info = DexSellLimitOrderTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.srcRegId = info.srcRegId; + this.coinSymbol = info.coinSymbol; + this.assetSymbol = info.assetSymbol; + this.assetAmount = info.assetAmount; + this.askPrice = info.askPrice; + this.feeSymbol = info.feeSymbol; + this.publicKey = info.publicKey; + this.network = info.network; + + return this; +}; + +DexSellLimitOrderTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = DexSellLimitOrderTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +DexSellLimitOrderTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + srcRegId: data.srcRegId, + coinSymbol: data.coinSymbol, + assetSymbol: data.assetSymbol, + assetAmount: data.assetAmount, + askPrice: data.askPrice, + publicKey: data.publicKey, + feeSymbol: data.feeSymbol + }; + return info; +}; + +DexSellLimitOrderTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var valueBuf = Util.writeVarInt(8, this.assetAmount) + writer.write(valueBuf) + + var askPrice = Util.writeVarInt(8, this.askPrice) + writer.write(askPrice) + + var serialBuf = writer.toBuffer() + + // console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +DexSellLimitOrderTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +DexSellLimitOrderTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var valueBuf = Util.writeVarInt(8, this.assetAmount) + writer.write(valueBuf) + + var askPrice = Util.writeVarInt(8, this.askPrice) + writer.write(askPrice) + + var sigBuf = this._Signtx(privateKey) + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default DexSellLimitOrderTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/dexsellmarketordertx.js b/src/backend/wallet/lib/transaction/dexsellmarketordertx.js new file mode 100644 index 0000000..40be80f --- /dev/null +++ b/src/backend/wallet/lib/transaction/dexsellmarketordertx.js @@ -0,0 +1,178 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; +import Address from '../address' + +var DexSellMarketOrderTx = function DexSellMarketOrderTx(arg) { + if (!(this instanceof DexSellMarketOrderTx)) { + return new DexSellMarketOrderTx(arg); + } + var info = DexSellMarketOrderTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.srcRegId = info.srcRegId; + this.coinSymbol = info.coinSymbol; + this.assetSymbol = info.assetSymbol; + this.assetAmount = info.assetAmount; + this.feeSymbol = info.feeSymbol; + this.publicKey = info.publicKey; + this.network = info.network; + + return this; +}; + +DexSellMarketOrderTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = DexSellMarketOrderTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for CommonTx'); + } + return info; +}; + +DexSellMarketOrderTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + srcRegId: data.srcRegId, + coinSymbol: data.coinSymbol, + assetSymbol: data.assetSymbol, + assetAmount: data.assetAmount, + publicKey: data.publicKey, + feeSymbol: data.feeSymbol + }; + return info; +}; + +DexSellMarketOrderTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var valueBuf = Util.writeVarInt(8, this.assetAmount) + writer.write(valueBuf) + + + var serialBuf = writer.toBuffer() + + // console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +DexSellMarketOrderTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +DexSellMarketOrderTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feeSymbol == null || _.isEmpty(this.feeSymbol)) return fasle; + writer.writeString(this.feeSymbol) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + if (this.coinSymbol.isNull || _.isEmpty(this.coinSymbol)) return false + writer.writeString(this.coinSymbol) + if (this.assetSymbol.isNull || _.isEmpty(this.assetSymbol)) return false + writer.writeString(this.assetSymbol) + + var valueBuf = Util.writeVarInt(8, this.assetAmount) + writer.write(valueBuf) + + var sigBuf = this._Signtx(privateKey) + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default DexSellMarketOrderTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/index.js b/src/backend/wallet/lib/transaction/index.js new file mode 100644 index 0000000..7dfa1c2 --- /dev/null +++ b/src/backend/wallet/lib/transaction/index.js @@ -0,0 +1,21 @@ +export { default } from './transaction'; + +export { default as Input } from './input'; + +export { default as Output } from './output'; + +export { default as UnspentOutput } from './unspentoutput'; + +export { default as Signature } from './signature'; + +export { default as Sighash } from './sighash'; + +export { default as SighashWitness } from './sighashwitness'; + +export { default as RegisterAccountTx } from './registeraccounttx'; + +export { default as CommonTx } from './commontx'; + +export { default as ContractTx } from './contracttx'; + +export { default as DelegateTx } from './delegatetx'; diff --git a/src/backend/wallet/lib/transaction/input/index.js b/src/backend/wallet/lib/transaction/input/index.js new file mode 100644 index 0000000..46b1983 --- /dev/null +++ b/src/backend/wallet/lib/transaction/input/index.js @@ -0,0 +1,9 @@ +export { default } from './input'; + +export { default as PublicKey } from './publickey'; + +export { default as PublicKeyHash } from './publickeyhash'; + +export { default as MultiSig } from './multisig'; + +export { default as MultiSigScriptHash } from './multisigscripthash'; diff --git a/src/backend/wallet/lib/transaction/input/input.js b/src/backend/wallet/lib/transaction/input/input.js new file mode 100644 index 0000000..264a3d4 --- /dev/null +++ b/src/backend/wallet/lib/transaction/input/input.js @@ -0,0 +1,219 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../../util/preconditions'; +import errors from '../../errors'; +import BufferWriter from '../../encoding/bufferwriter'; +import buffer from 'buffer'; +import BufferUtil from '../../util/buffer'; +import JSUtil from '../../util/js'; +import Script from '../../script'; +import Sighash from '../sighash'; +import Output from '../output'; + +var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1; +var DEFAULT_RBF_SEQNUMBER = MAXINT - 2; +var DEFAULT_SEQNUMBER = MAXINT; +var DEFAULT_LOCKTIME_SEQNUMBER = MAXINT - 1; + +function Input(params) { + if (!(this instanceof Input)) { + return new Input(params); + } + if (params) { + return this._fromObject(params); + } +} + +Input.MAXINT = MAXINT; +Input.DEFAULT_SEQNUMBER = DEFAULT_SEQNUMBER; +Input.DEFAULT_LOCKTIME_SEQNUMBER = DEFAULT_LOCKTIME_SEQNUMBER; +Input.DEFAULT_RBF_SEQNUMBER = DEFAULT_RBF_SEQNUMBER; + +Object.defineProperty(Input.prototype, 'script', { + configurable: false, + enumerable: true, + get: function () { + if (this.isNull()) { + return null; + } + if (!this._script) { + this._script = new Script(this._scriptBuffer); + this._script._isInput = true; + } + return this._script; + } +}); + +Input.fromObject = function (obj) { + $.checkArgument(_.isObject(obj)); + var input = new Input(); + return input._fromObject(obj); +}; + +Input.prototype._fromObject = function (params) { + var prevTxId; + if (_.isString(params.prevTxId) && JSUtil.isHexa(params.prevTxId)) { + prevTxId = new buffer.Buffer(params.prevTxId, 'hex'); + } else { + prevTxId = params.prevTxId; + } + this.witnesses = []; + this.output = params.output ? + (params.output instanceof Output ? params.output : new Output(params.output)) : undefined; + this.prevTxId = prevTxId || params.txidbuf; + this.outputIndex = _.isUndefined(params.outputIndex) ? params.txoutnum : params.outputIndex; + this.sequenceNumber = _.isUndefined(params.sequenceNumber) ? + (_.isUndefined(params.seqnum) ? DEFAULT_SEQNUMBER : params.seqnum) : params.sequenceNumber; + if (_.isUndefined(params.script) && _.isUndefined(params.scriptBuffer)) { + throw new errors.Transaction.Input.MissingScript(); + } + this.setScript(params.scriptBuffer || params.script); + return this; +}; + +Input.prototype.toObject = Input.prototype.toJSON = function toObject() { + var obj = { + prevTxId: this.prevTxId.toString('hex'), + outputIndex: this.outputIndex, + sequenceNumber: this.sequenceNumber, + script: this._scriptBuffer.toString('hex'), + }; + // add human readable form if input contains valid script + if (this.script) { + obj.scriptString = this.script.toString(); + } + if (this.output) { + obj.output = this.output.toObject(); + } + return obj; +}; + +Input.fromBufferReader = function (br) { + var input = new Input(); + input.prevTxId = br.readReverse(32); + input.outputIndex = br.readUInt32LE(); + input._scriptBuffer = br.readVarLengthBuffer(); + input.sequenceNumber = br.readUInt32LE(); + // TODO: return different classes according to which input it is + // e.g: CoinbaseInput, PublicKeyHashInput, MultiSigScriptHashInput, etc. + return input; +}; + +Input.prototype.toBufferWriter = function (writer) { + if (!writer) { + writer = new BufferWriter(); + } + writer.writeReverse(this.prevTxId); + writer.writeUInt32LE(this.outputIndex); + var script = this._scriptBuffer; + writer.writeVarintNum(script.length); + writer.write(script); + writer.writeUInt32LE(this.sequenceNumber); + return writer; +}; + +Input.prototype.setScript = function (script) { + this._script = null; + if (script instanceof Script) { + this._script = script; + this._script._isInput = true; + this._scriptBuffer = script.toBuffer(); + } else if (JSUtil.isHexa(script)) { + // hex string script + this._scriptBuffer = new buffer.Buffer(script, 'hex'); + } else if (_.isString(script)) { + // human readable string script + this._script = new Script(script); + this._script._isInput = true; + this._scriptBuffer = this._script.toBuffer(); + } else if (BufferUtil.isBuffer(script)) { + // buffer script + this._scriptBuffer = new buffer.Buffer(script); + } else { + throw new TypeError('Invalid argument type: script'); + } + return this; +}; + +/** + * Retrieve signatures for the provided PrivateKey. + * + * @param {Transaction} transaction - the transaction to be signed + * @param {PrivateKey} privateKey - the private key to use when signing + * @param {number} inputIndex - the index of this input in the provided transaction + * @param {number} sigType - defaults to Signature.SIGHASH_ALL + * @param {Buffer} addressHash - if provided, don't calculate the hash of the + * public key associated with the private key provided + * @abstract + */ +Input.prototype.getSignatures = function () { + throw new errors.AbstractMethodInvoked( + 'Trying to sign unsupported output type (only P2PKH and P2SH multisig inputs are supported)' + + ' for input: ' + JSON.stringify(this) + ); +}; + +Input.prototype.getSatoshisBuffer = function () { + $.checkState(this.output instanceof Output); + $.checkState(this.output._satoshisBN); + return new BufferWriter().writeUInt64LEBN(this.output._satoshisBN).toBuffer(); +}; + + +Input.prototype.isFullySigned = function () { + throw new errors.AbstractMethodInvoked('Input#isFullySigned'); +}; + +Input.prototype.isFinal = function () { + return this.sequenceNumber !== 4294967295; +}; + +Input.prototype.addSignature = function () { + throw new errors.AbstractMethodInvoked('Input#addSignature'); +}; + +Input.prototype.clearSignatures = function () { + throw new errors.AbstractMethodInvoked('Input#clearSignatures'); +}; + +Input.prototype.hasWitnesses = function () { + if (this.witnesses && this.witnesses.length > 0) { + return true; + } + return false; +}; + +Input.prototype.getWitnesses = function () { + return this.witnesses; +}; + +Input.prototype.setWitnesses = function (witnesses) { + this.witnesses = witnesses; +}; + +Input.prototype.isValidSignature = function (transaction, signature) { + // FIXME: Refactor signature so this is not necessary + signature.signature.nhashtype = signature.sigtype; + return Sighash.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + this.output.script + ); +}; + +/** + * @returns true if this is a coinbase input (represents no input) + */ +Input.prototype.isNull = function () { + return this.prevTxId.toString('hex') === '0000000000000000000000000000000000000000000000000000000000000000' && + this.outputIndex === 0xffffffff; +}; + +Input.prototype._estimateSize = function () { + return this.toBufferWriter().toBuffer().length; +}; + +export default Input; diff --git a/src/backend/wallet/lib/transaction/input/multisig.js b/src/backend/wallet/lib/transaction/input/multisig.js new file mode 100644 index 0000000..ab7746e --- /dev/null +++ b/src/backend/wallet/lib/transaction/input/multisig.js @@ -0,0 +1,211 @@ +'use strict'; + +import _ from 'lodash'; +import inherits from 'inherits'; +import Transaction from '../transaction'; +import Input from './input'; +import Output from '../output'; +import $ from '../../util/preconditions'; + +import Script from '../../script'; +import Signature from '../../crypto/signature'; +import Sighash from '../sighash'; +import PublicKey from '../../publickey'; +import BufferUtil from '../../util/buffer'; +import TransactionSignature from '../signature'; + +/** + * @constructor + */ +function MultiSigInput(input, pubkeys, threshold, signatures) { + Input.apply(this, arguments); + var self = this; + pubkeys = pubkeys || input.publicKeys; + threshold = threshold || input.threshold; + signatures = signatures || input.signatures; + this.publicKeys = _.sortBy(pubkeys, function (publicKey) { return publicKey.toString('hex'); }); + $.checkState(Script.buildMultisigOut(this.publicKeys, threshold).equals(this.output.script), + 'Provided public keys don\'t match to the provided output script'); + this.publicKeyIndex = {}; + _.each(this.publicKeys, function (publicKey, index) { + self.publicKeyIndex[publicKey.toString()] = index; + }); + this.threshold = threshold; + // Empty array of signatures + this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length); +} +inherits(MultiSigInput, Input); + +MultiSigInput.prototype.toObject = function () { + var obj = Input.prototype.toObject.apply(this, arguments); + obj.threshold = this.threshold; + obj.publicKeys = _.map(this.publicKeys, function (publicKey) { return publicKey.toString(); }); + obj.signatures = this._serializeSignatures(); + return obj; +}; + +MultiSigInput.prototype._deserializeSignatures = function (signatures) { + return _.map(signatures, function (signature) { + if (!signature) { + return undefined; + } + return new TransactionSignature(signature); + }); +}; + +MultiSigInput.prototype._serializeSignatures = function () { + return _.map(this.signatures, function (signature) { + if (!signature) { + return undefined; + } + return signature.toObject(); + }); +}; + +MultiSigInput.prototype.getSignatures = function (transaction, privateKey, index, sigtype) { + $.checkState(this.output instanceof Output); + sigtype = sigtype || Signature.SIGHASH_ALL; + + var self = this; + var results = []; + _.each(this.publicKeys, function (publicKey) { + if (publicKey.toString() === privateKey.publicKey.toString()) { + results.push(new TransactionSignature({ + publicKey: privateKey.publicKey, + prevTxId: self.prevTxId, + outputIndex: self.outputIndex, + inputIndex: index, + signature: Sighash.sign(transaction, privateKey, sigtype, index, self.output.script), + sigtype: sigtype + })); + } + }); + + return results; +}; + +MultiSigInput.prototype.addSignature = function (transaction, signature) { + $.checkState(!this.isFullySigned(), 'All needed signatures have already been added'); + $.checkArgument(!_.isUndefined(this.publicKeyIndex[signature.publicKey.toString()]), + 'Signature has no matching public key'); + $.checkState(this.isValidSignature(transaction, signature)); + this.signatures[this.publicKeyIndex[signature.publicKey.toString()]] = signature; + this._updateScript(); + return this; +}; + +MultiSigInput.prototype._updateScript = function () { + this.setScript(Script.buildMultisigIn( + this.publicKeys, + this.threshold, + this._createSignatures() + )); + return this; +}; + +MultiSigInput.prototype._createSignatures = function () { + return _.map( + _.filter(this.signatures, function (signature) { return !_.isUndefined(signature); }), + function (signature) { + return BufferUtil.concat([ + signature.signature.toDER(), + BufferUtil.integerAsSingleByteBuffer(signature.sigtype) + ]); + } + ); +}; + +MultiSigInput.prototype.clearSignatures = function () { + this.signatures = new Array(this.publicKeys.length); + this._updateScript(); +}; + +MultiSigInput.prototype.isFullySigned = function () { + return this.countSignatures() === this.threshold; +}; + +MultiSigInput.prototype.countMissingSignatures = function () { + return this.threshold - this.countSignatures(); +}; + +MultiSigInput.prototype.countSignatures = function () { + return _.reduce(this.signatures, function (sum, signature) { + return sum + (!!signature); + }, 0); +}; + +MultiSigInput.prototype.publicKeysWithoutSignature = function () { + var self = this; + return _.filter(this.publicKeys, function (publicKey) { + return !(self.signatures[self.publicKeyIndex[publicKey.toString()]]); + }); +}; + +MultiSigInput.prototype.isValidSignature = function (transaction, signature) { + // FIXME: Refactor signature so this is not necessary + signature.signature.nhashtype = signature.sigtype; + return Sighash.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + this.output.script + ); +}; + +/** + * + * @param {Buffer[]} signatures + * @param {PublicKey[]} publicKeys + * @param {Transaction} transaction + * @param {Integer} inputIndex + * @param {Input} input + * @returns {TransactionSignature[]} + */ +MultiSigInput.normalizeSignatures = function (transaction, input, inputIndex, signatures, publicKeys) { + return publicKeys.map(function (pubKey) { + var signatureMatch = null; + signatures = signatures.filter(function (signatureBuffer) { + if (signatureMatch) { + return true; + } + + var signature = new TransactionSignature({ + signature: Signature.fromTxFormat(signatureBuffer), + publicKey: pubKey, + prevTxId: input.prevTxId, + outputIndex: input.outputIndex, + inputIndex: inputIndex, + sigtype: Signature.SIGHASH_ALL + }); + + signature.signature.nhashtype = signature.sigtype; + var isMatch = Sighash.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + input.output.script + ); + + if (isMatch) { + signatureMatch = signature; + return false; + } + + return true; + }); + + return signatureMatch ? signatureMatch : null; + }); +}; + +MultiSigInput.OPCODES_SIZE = 1; // 0 +MultiSigInput.SIGNATURE_SIZE = 73; // size (1) + DER (<=72) + +MultiSigInput.prototype._estimateSize = function () { + return MultiSigInput.OPCODES_SIZE + + this.threshold * MultiSigInput.SIGNATURE_SIZE; +}; + +export default MultiSigInput; diff --git a/src/backend/wallet/lib/transaction/input/multisigscripthash.js b/src/backend/wallet/lib/transaction/input/multisigscripthash.js new file mode 100644 index 0000000..70701d5 --- /dev/null +++ b/src/backend/wallet/lib/transaction/input/multisigscripthash.js @@ -0,0 +1,241 @@ +'use strict'; + +/* jshint maxparams:5 */ + +import _ from 'lodash'; +import inherits from 'inherits'; +import Input from './input'; +import Output from '../output'; +import $ from '../../util/preconditions'; + +import Script from '../../script'; +import Signature from '../../crypto/signature'; +import Sighash from '../sighash'; +import SighashWitness from '../sighashwitness'; +import BufferWriter from '../../encoding/bufferwriter'; +import BufferUtil from '../../util/buffer'; +import TransactionSignature from '../signature'; + +/** + * @constructor + */ +function MultiSigScriptHashInput(input, pubkeys, threshold, signatures, nestedWitness) { + /* jshint maxstatements:20 */ + Input.apply(this, arguments); + var self = this; + pubkeys = pubkeys || input.publicKeys; + threshold = threshold || input.threshold; + signatures = signatures || input.signatures; + this.nestedWitness = nestedWitness ? true : false; + this.publicKeys = _.sortBy(pubkeys, function (publicKey) { return publicKey.toString('hex'); }); + this.redeemScript = Script.buildMultisigOut(this.publicKeys, threshold); + if (this.nestedWitness) { + var nested = Script.buildWitnessMultisigOutFromScript(this.redeemScript); + $.checkState(Script.buildScriptHashOut(nested).equals(this.output.script), + 'Provided public keys don\'t hash to the provided output (nested witness)'); + var scriptSig = new Script(); + scriptSig.add(nested.toBuffer()); + this.setScript(scriptSig); + } else { + $.checkState(Script.buildScriptHashOut(this.redeemScript).equals(this.output.script), + 'Provided public keys don\'t hash to the provided output'); + } + + this.publicKeyIndex = {}; + _.each(this.publicKeys, function (publicKey, index) { + self.publicKeyIndex[publicKey.toString()] = index; + }); + this.threshold = threshold; + // Empty array of signatures + this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length); +} +inherits(MultiSigScriptHashInput, Input); + +MultiSigScriptHashInput.prototype.toObject = function () { + var obj = Input.prototype.toObject.apply(this, arguments); + obj.threshold = this.threshold; + obj.publicKeys = _.map(this.publicKeys, function (publicKey) { return publicKey.toString(); }); + obj.signatures = this._serializeSignatures(); + return obj; +}; + +MultiSigScriptHashInput.prototype._deserializeSignatures = function (signatures) { + return _.map(signatures, function (signature) { + if (!signature) { + return undefined; + } + return new TransactionSignature(signature); + }); +}; + +MultiSigScriptHashInput.prototype._serializeSignatures = function () { + return _.map(this.signatures, function (signature) { + if (!signature) { + return undefined; + } + return signature.toObject(); + }); +}; + +MultiSigScriptHashInput.prototype.getScriptCode = function () { + var writer = new BufferWriter(); + if (!this.redeemScript.hasCodeseparators()) { + var redeemScriptBuffer = this.redeemScript.toBuffer(); + writer.writeVarintNum(redeemScriptBuffer.length); + writer.write(redeemScriptBuffer); + } else { + throw new Error('@TODO'); + } + return writer.toBuffer(); +}; + +MultiSigScriptHashInput.prototype.getSighash = function (transaction, privateKey, index, sigtype) { + var self = this; + var hash; + if (self.nestedWitness) { + var scriptCode = self.getScriptCode(); + var satoshisBuffer = self.getSatoshisBuffer(); + hash = SighashWitness.sighash(transaction, sigtype, index, scriptCode, satoshisBuffer); + } else { + hash = Sighash.sighash(transaction, sigtype, index, self.redeemScript); + } + return hash; +}; + +MultiSigScriptHashInput.prototype.getSignatures = function (transaction, privateKey, index, sigtype) { + $.checkState(this.output instanceof Output); + sigtype = sigtype || Signature.SIGHASH_ALL; + + var self = this; + var results = []; + _.each(this.publicKeys, function (publicKey) { + if (publicKey.toString() === privateKey.publicKey.toString()) { + var signature; + if (self.nestedWitness) { + var scriptCode = self.getScriptCode(); + var satoshisBuffer = self.getSatoshisBuffer(); + signature = SighashWitness.sign(transaction, privateKey, sigtype, index, scriptCode, satoshisBuffer); + } else { + signature = Sighash.sign(transaction, privateKey, sigtype, index, self.redeemScript); + } + results.push(new TransactionSignature({ + publicKey: privateKey.publicKey, + prevTxId: self.prevTxId, + outputIndex: self.outputIndex, + inputIndex: index, + signature: signature, + sigtype: sigtype + })); + } + }); + return results; +}; + +MultiSigScriptHashInput.prototype.addSignature = function (transaction, signature) { + $.checkState(!this.isFullySigned(), 'All needed signatures have already been added'); + $.checkArgument(!_.isUndefined(this.publicKeyIndex[signature.publicKey.toString()]), + 'Signature has no matching public key'); + $.checkState(this.isValidSignature(transaction, signature)); + this.signatures[this.publicKeyIndex[signature.publicKey.toString()]] = signature; + this._updateScript(); + return this; +}; + +MultiSigScriptHashInput.prototype._updateScript = function () { + if (this.nestedWitness) { + var stack = [ + new Buffer(0), + ]; + var signatures = this._createSignatures(); + for (var i = 0; i < signatures.length; i++) { + stack.push(signatures[i]); + } + stack.push(this.redeemScript.toBuffer()); + this.setWitnesses(stack); + } else { + var scriptSig = Script.buildP2SHMultisigIn( + this.publicKeys, + this.threshold, + this._createSignatures(), + { cachedMultisig: this.redeemScript } + ); + this.setScript(scriptSig); + } + return this; +}; + +MultiSigScriptHashInput.prototype._createSignatures = function () { + return _.map( + _.filter(this.signatures, function (signature) { return !_.isUndefined(signature); }), + function (signature) { + return BufferUtil.concat([ + signature.signature.toDER(), + BufferUtil.integerAsSingleByteBuffer(signature.sigtype) + ]); + } + ); +}; + +MultiSigScriptHashInput.prototype.clearSignatures = function () { + this.signatures = new Array(this.publicKeys.length); + this._updateScript(); +}; + +MultiSigScriptHashInput.prototype.isFullySigned = function () { + return this.countSignatures() === this.threshold; +}; + +MultiSigScriptHashInput.prototype.countMissingSignatures = function () { + return this.threshold - this.countSignatures(); +}; + +MultiSigScriptHashInput.prototype.countSignatures = function () { + return _.reduce(this.signatures, function (sum, signature) { + return sum + (!!signature); + }, 0); +}; + +MultiSigScriptHashInput.prototype.publicKeysWithoutSignature = function () { + var self = this; + return _.filter(this.publicKeys, function (publicKey) { + return !(self.signatures[self.publicKeyIndex[publicKey.toString()]]); + }); +}; + +MultiSigScriptHashInput.prototype.isValidSignature = function (transaction, signature) { + if (this.nestedWitness) { + signature.signature.nhashtype = signature.sigtype; + var scriptCode = this.getScriptCode(); + var satoshisBuffer = this.getSatoshisBuffer(); + return SighashWitness.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + scriptCode, + satoshisBuffer + ); + } else { + // FIXME: Refactor signature so this is not necessary + signature.signature.nhashtype = signature.sigtype; + return Sighash.verify( + transaction, + signature.signature, + signature.publicKey, + signature.inputIndex, + this.redeemScript + ); + } +}; + +MultiSigScriptHashInput.OPCODES_SIZE = 7; // serialized size (<=3) + 0 .. N .. M OP_CHECKMULTISIG +MultiSigScriptHashInput.SIGNATURE_SIZE = 74; // size (1) + DER (<=72) + sighash (1) +MultiSigScriptHashInput.PUBKEY_SIZE = 34; // size (1) + DER (<=33) + +MultiSigScriptHashInput.prototype._estimateSize = function () { + return MultiSigScriptHashInput.OPCODES_SIZE + + this.threshold * MultiSigScriptHashInput.SIGNATURE_SIZE + + this.publicKeys.length * MultiSigScriptHashInput.PUBKEY_SIZE; +}; + +export default MultiSigScriptHashInput; diff --git a/src/backend/wallet/lib/transaction/input/publickey.js b/src/backend/wallet/lib/transaction/input/publickey.js new file mode 100644 index 0000000..700f9cd --- /dev/null +++ b/src/backend/wallet/lib/transaction/input/publickey.js @@ -0,0 +1,89 @@ +'use strict'; + +import inherits from 'inherits'; + +import $ from '../../util/preconditions'; +import BufferUtil from '../../util/buffer'; + +import Input from './input'; +import Output from '../output'; +import Sighash from '../sighash'; +import Script from '../../script'; +import Signature from '../../crypto/signature'; +import TransactionSignature from '../signature'; + +/** + * Represents a special kind of input of PayToPublicKey kind. + * @constructor + */ +function PublicKeyInput() { + Input.apply(this, arguments); +} +inherits(PublicKeyInput, Input); + +/** + * @param {Transaction} transaction - the transaction to be signed + * @param {PrivateKey} privateKey - the private key with which to sign the transaction + * @param {number} index - the index of the input in the transaction input vector + * @param {number=} sigtype - the type of signature, defaults to Signature.SIGHASH_ALL + * @return {Array} of objects that can be + */ +PublicKeyInput.prototype.getSignatures = function (transaction, privateKey, index, sigtype) { + $.checkState(this.output instanceof Output); + sigtype = sigtype || Signature.SIGHASH_ALL; + var publicKey = privateKey.toPublicKey(); + if (publicKey.toString() === this.output.script.getPublicKey().toString('hex')) { + return [new TransactionSignature({ + publicKey: publicKey, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex, + inputIndex: index, + signature: Sighash.sign(transaction, privateKey, sigtype, index, this.output.script), + sigtype: sigtype + })]; + } + return []; +}; + +/** + * Add the provided signature + * + * @param {Object} signature + * @param {PublicKey} signature.publicKey + * @param {Signature} signature.signature + * @param {number=} signature.sigtype + * @return {PublicKeyInput} this, for chaining + */ +PublicKeyInput.prototype.addSignature = function (transaction, signature) { + $.checkState(this.isValidSignature(transaction, signature), 'Signature is invalid'); + this.setScript(Script.buildPublicKeyIn( + signature.signature.toDER(), + signature.sigtype + )); + return this; +}; + +/** + * Clear the input's signature + * @return {PublicKeyHashInput} this, for chaining + */ +PublicKeyInput.prototype.clearSignatures = function () { + this.setScript(Script.empty()); + return this; +}; + +/** + * Query whether the input is signed + * @return {boolean} + */ +PublicKeyInput.prototype.isFullySigned = function () { + return this.script.isPublicKeyIn(); +}; + +PublicKeyInput.SCRIPT_MAX_SIZE = 73; // sigsize (1 + 72) + +PublicKeyInput.prototype._estimateSize = function () { + return PublicKeyInput.SCRIPT_MAX_SIZE; +}; + +export default PublicKeyInput; diff --git a/src/backend/wallet/lib/transaction/input/publickeyhash.js b/src/backend/wallet/lib/transaction/input/publickeyhash.js new file mode 100644 index 0000000..d71105f --- /dev/null +++ b/src/backend/wallet/lib/transaction/input/publickeyhash.js @@ -0,0 +1,95 @@ +'use strict'; + +import inherits from 'inherits'; + +import $ from '../../util/preconditions'; +import BufferUtil from '../../util/buffer'; + +import Hash from '../../crypto/hash'; +import Input from './input'; +import Output from '../output'; +import Sighash from '../sighash'; +import Script from '../../script'; +import Signature from '../../crypto/signature'; +import TransactionSignature from '../signature'; + +/** + * Represents a special kind of input of PayToPublicKeyHash kind. + * @constructor + */ +function PublicKeyHashInput() { + Input.apply(this, arguments); +} +inherits(PublicKeyHashInput, Input); + +/* jshint maxparams: 5 */ +/** + * @param {Transaction} transaction - the transaction to be signed + * @param {PrivateKey} privateKey - the private key with which to sign the transaction + * @param {number} index - the index of the input in the transaction input vector + * @param {number=} sigtype - the type of signature, defaults to Signature.SIGHASH_ALL + * @param {Buffer=} hashData - the precalculated hash of the public key associated with the privateKey provided + * @return {Array} of objects that can be + */ +PublicKeyHashInput.prototype.getSignatures = function (transaction, privateKey, index, sigtype, hashData) { + $.checkState(this.output instanceof Output); + hashData = hashData || Hash.sha256ripemd160(privateKey.publicKey.toBuffer()); + sigtype = sigtype || Signature.SIGHASH_ALL; + + if (BufferUtil.equals(hashData, this.output.script.getPublicKeyHash())) { + return [new TransactionSignature({ + publicKey: privateKey.publicKey, + prevTxId: this.prevTxId, + outputIndex: this.outputIndex, + inputIndex: index, + signature: Sighash.sign(transaction, privateKey, sigtype, index, this.output.script), + sigtype: sigtype + })]; + } + return []; +}; +/* jshint maxparams: 3 */ + +/** + * Add the provided signature + * + * @param {Object} signature + * @param {PublicKey} signature.publicKey + * @param {Signature} signature.signature + * @param {number=} signature.sigtype + * @return {PublicKeyHashInput} this, for chaining + */ +PublicKeyHashInput.prototype.addSignature = function (transaction, signature) { + $.checkState(this.isValidSignature(transaction, signature), 'Signature is invalid'); + this.setScript(Script.buildPublicKeyHashIn( + signature.publicKey, + signature.signature.toDER(), + signature.sigtype + )); + return this; +}; + +/** + * Clear the input's signature + * @return {PublicKeyHashInput} this, for chaining + */ +PublicKeyHashInput.prototype.clearSignatures = function () { + this.setScript(Script.empty()); + return this; +}; + +/** + * Query whether the input is signed + * @return {boolean} + */ +PublicKeyHashInput.prototype.isFullySigned = function () { + return this.script.isPublicKeyHashIn(); +}; + +PublicKeyHashInput.SCRIPT_MAX_SIZE = 73 + 34; // sigsize (1 + 72) + pubkey (1 + 33) + +PublicKeyHashInput.prototype._estimateSize = function () { + return PublicKeyHashInput.SCRIPT_MAX_SIZE; +}; + +export default PublicKeyHashInput; diff --git a/src/backend/wallet/lib/transaction/output.js b/src/backend/wallet/lib/transaction/output.js new file mode 100644 index 0000000..2d8e208 --- /dev/null +++ b/src/backend/wallet/lib/transaction/output.js @@ -0,0 +1,168 @@ +'use strict'; + +import _ from 'lodash'; +import BN from '../crypto/bn'; +import buffer from 'buffer'; +import bufferUtil from '../util/buffer'; +import JSUtil from '../util/js'; +import BufferWriter from '../encoding/bufferwriter'; +import Script from '../script'; +import $ from '../util/preconditions'; +import errors from '../errors'; + +var MAX_SAFE_INTEGER = 0x1fffffffffffff; + +function Output(args) { + if (!(this instanceof Output)) { + return new Output(args); + } + if (_.isObject(args)) { + this.satoshis = args.satoshis; + if (bufferUtil.isBuffer(args.script)) { + this._scriptBuffer = args.script; + } else { + var script; + if (_.isString(args.script) && JSUtil.isHexa(args.script)) { + script = new buffer.Buffer(args.script, 'hex'); + } else { + script = args.script; + } + this.setScript(script); + } + } else { + throw new TypeError('Unrecognized argument for Output'); + } +} + +Object.defineProperty(Output.prototype, 'script', { + configurable: false, + enumerable: true, + get: function () { + if (this._script) { + return this._script; + } else { + this.setScriptFromBuffer(this._scriptBuffer); + return this._script; + } + + } +}); + +Object.defineProperty(Output.prototype, 'satoshis', { + configurable: false, + enumerable: true, + get: function () { + return this._satoshis; + }, + set: function (num) { + if (num instanceof BN) { + this._satoshisBN = num; + this._satoshis = num.toNumber(); + } else if (_.isString(num)) { + this._satoshis = parseInt(num); + this._satoshisBN = BN.fromNumber(this._satoshis); + } else { + $.checkArgument( + JSUtil.isNaturalNumber(num), + 'Output satoshis is not a natural number' + ); + this._satoshisBN = BN.fromNumber(num); + this._satoshis = num; + } + $.checkState( + JSUtil.isNaturalNumber(this._satoshis), + 'Output satoshis is not a natural number' + ); + } +}); + +Output.prototype.invalidSatoshis = function () { + if (this._satoshis > MAX_SAFE_INTEGER) { + return 'transaction txout satoshis greater than max safe integer'; + } + if (this._satoshis !== this._satoshisBN.toNumber()) { + return 'transaction txout satoshis has corrupted value'; + } + if (this._satoshis < 0) { + return 'transaction txout negative'; + } + return false; +}; + +Output.prototype.toObject = Output.prototype.toJSON = function toObject() { + var obj = { + satoshis: this.satoshis + }; + obj.script = this._scriptBuffer.toString('hex'); + return obj; +}; + +Output.fromObject = function (data) { + return new Output(data); +}; + +Output.prototype.setScriptFromBuffer = function (buffer) { + this._scriptBuffer = buffer; + try { + this._script = Script.fromBuffer(this._scriptBuffer); + this._script._isOutput = true; + } catch (e) { + if (e instanceof errors.Script.InvalidBuffer) { + this._script = null; + } else { + throw e; + } + } +}; + +Output.prototype.setScript = function (script) { + if (script instanceof Script) { + this._scriptBuffer = script.toBuffer(); + this._script = script; + this._script._isOutput = true; + } else if (_.isString(script)) { + this._script = Script.fromString(script); + this._scriptBuffer = this._script.toBuffer(); + this._script._isOutput = true; + } else if (bufferUtil.isBuffer(script)) { + this.setScriptFromBuffer(script); + } else { + throw new TypeError('Invalid argument type: script'); + } + return this; +}; + +Output.prototype.inspect = function () { + var scriptStr; + if (this.script) { + scriptStr = this.script.inspect(); + } else { + scriptStr = this._scriptBuffer.toString('hex'); + } + return ''; +}; + +Output.fromBufferReader = function (br) { + var obj = {}; + obj.satoshis = br.readUInt64LEBN(); + var size = br.readVarintNum(); + if (size !== 0) { + obj.script = br.read(size); + } else { + obj.script = new buffer.Buffer([]); + } + return new Output(obj); +}; + +Output.prototype.toBufferWriter = function (writer) { + if (!writer) { + writer = new BufferWriter(); + } + writer.writeUInt64LEBN(this._satoshisBN); + var script = this._scriptBuffer; + writer.writeVarintNum(script.length); + writer.write(script); + return writer; +}; + +export default Output; diff --git a/src/backend/wallet/lib/transaction/registeraccounttx.js b/src/backend/wallet/lib/transaction/registeraccounttx.js new file mode 100644 index 0000000..478d794 --- /dev/null +++ b/src/backend/wallet/lib/transaction/registeraccounttx.js @@ -0,0 +1,123 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import Signature from '../crypto/signature'; +import BufferWriter from '../encoding/bufferwriter'; + +var RegisterAccountTx = function RegisterAccountTx(arg) { + if (!(this instanceof RegisterAccountTx)) { + return new RegisterAccountTx(arg); + } + var info = RegisterAccountTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.fees = info.fees; + this.pubkey = info.pubkey; + this.minerPubkey = info.minerPubkey; + + + return this; +}; + +RegisterAccountTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = RegisterAccountTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for RegisterAccountTx'); + } + return info; +}; + +RegisterAccountTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + fees: data.fees, + pubkey: data.pubkey, + minerPubkey: data.minerPubkey + }; + return info; +}; + +RegisterAccountTx.prototype._SignatureHash = function () { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + var pubkeyBuf = new Buffer(this.pubkey, 'hex') + var len = pubkeyBuf.length + writer.writeVarintNum(len) + writer.write(pubkeyBuf); + + var minerBuf = new Buffer(this.minerPubkey, 'hex') + len = minerBuf.length + writer.writeVarintNum(len) + if (len > 0) { + writer.write(minerBuf); + } + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + var serialBuf = writer.toBuffer() + + return Hash.sha256sha256(serialBuf); +} + +RegisterAccountTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + //console.log('hash buf: ' + hashbuf.toString('hex')) + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +RegisterAccountTx.prototype.SerializeTx = function (privateKey) { + var writer = new BufferWriter(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + var pubkeyBuf = new Buffer(this.pubkey, 'hex') + var len = pubkeyBuf.length + writer.writeVarintNum(len) + writer.write(pubkeyBuf); + + var minerBuf = new Buffer(this.minerPubkey, 'hex') + len = minerBuf.length + writer.writeVarintNum(len) + if (len > 0) { + writer.write(minerBuf); + } + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + + var sigBuf = this._Signtx(privateKey) + + len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default RegisterAccountTx; diff --git a/src/backend/wallet/lib/transaction/registerapptx.js b/src/backend/wallet/lib/transaction/registerapptx.js new file mode 100644 index 0000000..8c56022 --- /dev/null +++ b/src/backend/wallet/lib/transaction/registerapptx.js @@ -0,0 +1,114 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import WriterHelper from '../util/writerhelper'; +import errors from '../errors'; + +var RegisterAppTx = function RegisterAppTx(arg) { + if (!(this instanceof RegisterAppTx)) { + return new RegisterAppTx(arg); + } + var info = RegisterAppTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.regAcctId = info.regAcctId; + this.script = info.script; + this.scryptDesc = info.scriptDesc; + this.publicKey = info.publicKey; + this.fees = info.fees; + + return this; +}; + +RegisterAppTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = RegisterAppTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for RegisterAppTx'); + } + return info; +}; + +RegisterAppTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + // if (!Util.checkRegId(data.regAcctId)) { + // throw new errors.InvalidArgument("regAcctId", "Invalid reg id"); + // } + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + regAcctId: data.regAcctId, + script: data.script, + scryptDesc: data.scriptDesc, + publicKey: data.publicKey, + fees: data.fees + }; + + return info; +}; + +RegisterAppTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + //writer.writeVarintNum(this.nVersion) + // nVersion should use VARINT format + writer.writeVarInt(4, this.nVersion) + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nValidHeight) + writer.writeRegId(this.regAcctId) + // write script and desc + var scriptWriter = new WriterHelper(); + scriptWriter.writeString(this.script) + scriptWriter.writeString(this.scriptDesc) + writer.writeBuf(scriptWriter.toBuffer()) + + writer.writeVarInt(8, this.fees) + + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +RegisterAppTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +RegisterAppTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeUInt8(this.nTxType) + writer.writeVarintNum(this.nVersion) + writer.writeVarInt(4, this.nValidHeight) + writer.writeRegId(this.regAcctId) + // write script and desc + var scriptWriter = new WriterHelper(); + scriptWriter.writeString(this.script) + scriptWriter.writeString(this.scriptDesc) + writer.writeBuf(scriptWriter.toBuffer()) + + writer.writeVarInt(8, this.fees) + + var sigBuf = this._Signtx(privateKey) + writer.writeBuf(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default RegisterAppTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/sighash.js b/src/backend/wallet/lib/transaction/sighash.js new file mode 100644 index 0000000..d7998bf --- /dev/null +++ b/src/backend/wallet/lib/transaction/sighash.js @@ -0,0 +1,138 @@ +'use strict'; + +import buffer from 'buffer'; + +import Signature from '../crypto/signature'; +import Script from '../script'; +import Output from './output'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import $ from '../util/preconditions'; +import _ from 'lodash'; +import Transaction from './transaction'; +import input from './input'; + +var SIGHASH_SINGLE_BUG = '0000000000000000000000000000000000000000000000000000000000000001'; +var BITS_64_ON = 'ffffffffffffffff'; + +/** + * Returns a buffer of length 32 bytes with the hash that needs to be signed + * for OP_CHECKSIG. + * + * @name Signing.sighash + * @param {Transaction} transaction the transaction to sign + * @param {number} sighashType the type of the hash + * @param {number} inputNumber the input index for the signature + * @param {Script} subscript the script that will be signed + */ +var sighash = function sighash(transaction, sighashType, inputNumber, subscript) { + var Transaction = Transaction; + var Input = input; + + var i; + // Copy transaction + var txcopy = Transaction.shallowCopy(transaction); + + // Copy script + subscript = new Script(subscript); + subscript.removeCodeseparators(); + + for (i = 0; i < txcopy.inputs.length; i++) { + // Blank signatures for other inputs + txcopy.inputs[i] = new Input(txcopy.inputs[i]).setScript(Script.empty()); + } + + txcopy.inputs[inputNumber] = new Input(txcopy.inputs[inputNumber]).setScript(subscript); + + if ((sighashType & 31) === Signature.SIGHASH_NONE || + (sighashType & 31) === Signature.SIGHASH_SINGLE) { + + // clear all sequenceNumbers + for (i = 0; i < txcopy.inputs.length; i++) { + if (i !== inputNumber) { + txcopy.inputs[i].sequenceNumber = 0; + } + } + } + + if ((sighashType & 31) === Signature.SIGHASH_NONE) { + txcopy.outputs = []; + + } else if ((sighashType & 31) === Signature.SIGHASH_SINGLE) { + // The SIGHASH_SINGLE bug. + // https://bitcointalk.org/index.php?topic=260595.0 + if (inputNumber >= txcopy.outputs.length) { + return Buffer.from(SIGHASH_SINGLE_BUG, 'hex'); + } + + txcopy.outputs.length = inputNumber + 1; + + for (i = 0; i < inputNumber; i++) { + txcopy.outputs[i] = new Output({ + satoshis: BN.fromBuffer(new buffer.Buffer(BITS_64_ON, 'hex')), + script: Script.empty() + }); + } + } + + if (sighashType & Signature.SIGHASH_ANYONECANPAY) { + txcopy.inputs = [txcopy.inputs[inputNumber]]; + } + + var buf = new BufferWriter() + .write(txcopy.toBuffer()) + .writeInt32LE(sighashType) + .toBuffer(); + var ret = Hash.sha256sha256(buf); + ret = new BufferReader(ret).readReverse(); + return ret; +}; + +/** + * Create a signature + * + * @name Signing.sign + * @param {Transaction} transaction + * @param {PrivateKey} privateKey + * @param {number} sighash + * @param {number} inputIndex + * @param {Script} subscript + * @return {Signature} + */ +function sign(transaction, privateKey, sighashType, inputIndex, subscript) { + var hashbuf = sighash(transaction, sighashType, inputIndex, subscript); + var sig = ECDSA.sign(hashbuf, privateKey, 'little').set({ + nhashtype: sighashType + }); + return sig; +} + +/** + * Verify a signature + * + * @name Signing.verify + * @param {Transaction} transaction + * @param {Signature} signature + * @param {PublicKey} publicKey + * @param {number} inputIndex + * @param {Script} subscript + * @return {boolean} + */ +function verify(transaction, signature, publicKey, inputIndex, subscript) { + $.checkArgument(!_.isUndefined(transaction)); + $.checkArgument(!_.isUndefined(signature) && !_.isUndefined(signature.nhashtype)); + var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, subscript); + return ECDSA.verify(hashbuf, signature, publicKey, 'little'); +} + +/** + * @namespace Signing + */ +export default { + sighash: sighash, + sign: sign, + verify: verify +}; diff --git a/src/backend/wallet/lib/transaction/sighashwitness.js b/src/backend/wallet/lib/transaction/sighashwitness.js new file mode 100644 index 0000000..8c469bc --- /dev/null +++ b/src/backend/wallet/lib/transaction/sighashwitness.js @@ -0,0 +1,149 @@ +'use strict'; + +/* jshint maxparams:5 */ + +import Signature from '../crypto/signature'; +import Script from '../script'; +import Output from './output'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import BN from '../crypto/bn'; +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import $ from '../util/preconditions'; +import _ from 'lodash'; + +/** + * Returns a buffer of length 32 bytes with the hash that needs to be signed + * for witness programs as defined by: + * https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki + * + * @name Signing.sighash + * @param {Transaction} transaction the transaction to sign + * @param {number} sighashType the type of the hash + * @param {number} inputNumber the input index for the signature + * @param {Buffer} scriptCode + * @param {Buffer} satoshisBuffer + */ +var sighash = function sighash(transaction, sighashType, inputNumber, scriptCode, satoshisBuffer) { + /* jshint maxstatements: 50 */ + + var hashPrevouts; + var hashSequence; + var hashOutputs; + + if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { + var buffers = []; + for (var n = 0; n < transaction.inputs.length; n++) { + var input = transaction.inputs[n]; + var prevTxIdBuffer = new BufferReader(input.prevTxId).readReverse(); + buffers.push(prevTxIdBuffer); + var outputIndexBuffer = new Buffer(new Array(4)); + outputIndexBuffer.writeUInt32LE(input.outputIndex, 0); + buffers.push(outputIndexBuffer); + } + hashPrevouts = Hash.sha256sha256(Buffer.concat(buffers)); + } + + if (!(sighashType & Signature.SIGHASH_ANYONECANPAY) && + (sighashType & 0x1f) !== Signature.SIGHASH_SINGLE && (sighashType & 0x1f) !== Signature.SIGHASH_NONE) { + + var sequenceBuffers = []; + for (var m = 0; m < transaction.inputs.length; m++) { + var sequenceBuffer = new Buffer(new Array(4)); + sequenceBuffer.writeUInt32LE(transaction.inputs[m].sequenceNumber, 0); + sequenceBuffers.push(sequenceBuffer); + } + hashSequence = Hash.sha256sha256(Buffer.concat(sequenceBuffers)); + } + + var outputWriter = new BufferWriter(); + if ((sighashType & 0x1f) !== Signature.SIGHASH_SINGLE && (sighashType & 0x1f) !== Signature.SIGHASH_NONE) { + for (var p = 0; p < transaction.outputs.length; p++) { + transaction.outputs[p].toBufferWriter(outputWriter); + } + hashOutputs = Hash.sha256sha256(outputWriter.toBuffer()); + } else if ((sighashType & 0x1f) === Signature.SIGHASH_SINGLE && inputNumber < transaction.outputs.length) { + transaction.outputs[inputNumber].toBufferWriter(outputWriter); + hashOutputs = Hash.sha256sha256(outputWriter.toBuffer()); + } + + // Version + var writer = new BufferWriter(); + writer.writeUInt32LE(transaction.version); + + // Input prevouts/nSequence (none/all, depending on flags) + writer.write(hashPrevouts); + writer.write(hashSequence); + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + var outpointId = new BufferReader(transaction.inputs[inputNumber].prevTxId).readReverse(); + writer.write(outpointId); + writer.writeUInt32LE(transaction.inputs[inputNumber].outputIndex); + + writer.write(scriptCode); + + writer.write(satoshisBuffer); + + writer.writeUInt32LE(transaction.inputs[inputNumber].sequenceNumber); + + // Outputs (none/one/all, depending on flags) + writer.write(hashOutputs); + + // Locktime + writer.writeUInt32LE(transaction.nLockTime); + + // Sighash type + writer.writeInt32LE(sighashType); + + return Hash.sha256sha256(writer.toBuffer()); + +}; + +/** + * Create a signature + * + * @name Signing.sign + * @param {Transaction} transaction + * @param {PrivateKey} privateKey + * @param {number} sighash + * @param {number} inputIndex + * @param {Script} subscript + * @return {Signature} + */ +function sign(transaction, privateKey, sighashType, inputIndex, scriptCode, satoshisBuffer) { + var hashbuf = sighash(transaction, sighashType, inputIndex, scriptCode, satoshisBuffer); + var sig = ECDSA.sign(hashbuf, privateKey).set({ + nhashtype: sighashType + }); + return sig; +} + +/** + * Verify a signature + * + * @name Signing.verify + * @param {Transaction} transaction + * @param {Signature} signature + * @param {PublicKey} publicKey + * @param {number} inputIndex + * @param {Script} subscript + * @return {boolean} + */ +function verify(transaction, signature, publicKey, inputIndex, scriptCode, satoshisBuffer) { + $.checkArgument(!_.isUndefined(transaction)); + $.checkArgument(!_.isUndefined(signature) && !_.isUndefined(signature.nhashtype)); + var hashbuf = sighash(transaction, signature.nhashtype, inputIndex, scriptCode, satoshisBuffer); + return ECDSA.verify(hashbuf, signature, publicKey); +} + +/** + * @namespace Signing + */ +export default { + sighash: sighash, + sign: sign, + verify: verify +}; diff --git a/src/backend/wallet/lib/transaction/signature.js b/src/backend/wallet/lib/transaction/signature.js new file mode 100644 index 0000000..d459622 --- /dev/null +++ b/src/backend/wallet/lib/transaction/signature.js @@ -0,0 +1,88 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import inherits from 'inherits'; +import BufferUtil from '../util/buffer'; +import JSUtil from '../util/js'; +import PublicKey from '../publickey'; +import errors from '../errors'; +import Signature from '../crypto/signature'; + +/** + * @desc + * Wrapper around Signature with fields related to signing a transaction specifically + * + * @param {Object|string|TransactionSignature} arg + * @constructor + */ +function TransactionSignature(arg) { + if (!(this instanceof TransactionSignature)) { + return new TransactionSignature(arg); + } + if (arg instanceof TransactionSignature) { + return arg; + } + if (_.isObject(arg)) { + return this._fromObject(arg); + } + throw new errors.InvalidArgument('TransactionSignatures must be instantiated from an object'); +} +inherits(TransactionSignature, Signature); + +TransactionSignature.prototype._fromObject = function (arg) { + this._checkObjectArgs(arg); + this.publicKey = new PublicKey(arg.publicKey); + this.prevTxId = BufferUtil.isBuffer(arg.prevTxId) ? arg.prevTxId : Buffer.from(arg.prevTxId, 'hex'); + this.outputIndex = arg.outputIndex; + this.inputIndex = arg.inputIndex; + this.signature = (arg.signature instanceof Signature) ? arg.signature : + BufferUtil.isBuffer(arg.signature) ? Signature.fromBuffer(arg.signature) : + Signature.fromString(arg.signature); + this.sigtype = arg.sigtype; + return this; +}; + +TransactionSignature.prototype._checkObjectArgs = function (arg) { + $.checkArgument(PublicKey(arg.publicKey), 'publicKey'); + $.checkArgument(!_.isUndefined(arg.inputIndex), 'inputIndex'); + $.checkArgument(!_.isUndefined(arg.outputIndex), 'outputIndex'); + $.checkState(_.isNumber(arg.inputIndex), 'inputIndex must be a number'); + $.checkState(_.isNumber(arg.outputIndex), 'outputIndex must be a number'); + $.checkArgument(arg.signature, 'signature'); + $.checkArgument(arg.prevTxId, 'prevTxId'); + $.checkState(arg.signature instanceof Signature || + BufferUtil.isBuffer(arg.signature) || + JSUtil.isHexa(arg.signature), 'signature must be a buffer or hexa value'); + $.checkState(BufferUtil.isBuffer(arg.prevTxId) || + JSUtil.isHexa(arg.prevTxId), 'prevTxId must be a buffer or hexa value'); + $.checkArgument(arg.sigtype, 'sigtype'); + $.checkState(_.isNumber(arg.sigtype), 'sigtype must be a number'); +}; + +/** + * Serializes a transaction to a plain JS object + * @return {Object} + */ +TransactionSignature.prototype.toObject = TransactionSignature.prototype.toJSON = function toObject() { + return { + publicKey: this.publicKey.toString(), + prevTxId: this.prevTxId.toString('hex'), + outputIndex: this.outputIndex, + inputIndex: this.inputIndex, + signature: this.signature.toString(), + sigtype: this.sigtype + }; +}; + +/** + * Builds a TransactionSignature from an object + * @param {Object} object + * @return {TransactionSignature} + */ +TransactionSignature.fromObject = function (object) { + $.checkArgument(object); + return new TransactionSignature(object); +}; + +export default TransactionSignature; diff --git a/src/backend/wallet/lib/transaction/transaction.js b/src/backend/wallet/lib/transaction/transaction.js new file mode 100644 index 0000000..c2ddf19 --- /dev/null +++ b/src/backend/wallet/lib/transaction/transaction.js @@ -0,0 +1,1334 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import buffer from 'buffer'; +import _compare from 'buffer-compare'; +var compare = Buffer.compare || _compare; + +import errors from '../errors'; +import BufferUtil from '../util/buffer'; +import JSUtil from '../util/js'; +import BufferReader from '../encoding/bufferreader'; +import BufferWriter from '../encoding/bufferwriter'; +import Hash from '../crypto/hash'; +import Signature from '../crypto/signature'; +import Sighash from './sighash'; +import SighashWitness from './sighashwitness'; + +import Address from '../address'; +import UnspentOutput from './unspentoutput'; +import Input from './input'; +var PublicKeyHashInput = Input.PublicKeyHash; +var PublicKeyInput = Input.PublicKey; +var MultiSigScriptHashInput = Input.MultiSigScriptHash; +var MultiSigInput = Input.MultiSig; +import Output from './output'; +import Script from '../script'; +import PrivateKey from '../privatekey'; +import BN from '../crypto/bn'; + +/** + * Represents a transaction, a set of inputs and outputs to change ownership of tokens + * + * @param {*} serialized + * @constructor + */ +function Transaction(serialized) { + if (!(this instanceof Transaction)) { + return new Transaction(serialized); + } + this.inputs = []; + this.outputs = []; + this._inputAmount = undefined; + this._outputAmount = undefined; + + if (serialized) { + if (serialized instanceof Transaction) { + return Transaction.shallowCopy(serialized); + } else if (JSUtil.isHexa(serialized)) { + this.fromString(serialized); + } else if (BufferUtil.isBuffer(serialized)) { + this.fromBuffer(serialized); + } else if (_.isObject(serialized)) { + this.fromObject(serialized); + } else { + throw new errors.InvalidArgument('Must provide an object or string to deserialize a transaction'); + } + } else { + this._newTransaction(); + } +} +var CURRENT_VERSION = 1; +var DEFAULT_NLOCKTIME = 0; +var MAX_BLOCK_SIZE = 1000000; + +// Minimum amount for an output for it not to be considered a dust output +Transaction.DUST_AMOUNT = 546; + +// Margin of error to allow fees in the vecinity of the expected value but doesn't allow a big difference +Transaction.FEE_SECURITY_MARGIN = 150; + +// max amount of satoshis in circulation +Transaction.MAX_MONEY = 21000000 * 1e8; + +// nlocktime limit to be considered block height rather than a timestamp +Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT = 5e8; + +// Max value for an unsigned 32 bit value +Transaction.NLOCKTIME_MAX_VALUE = 4294967295; + +// Value used for fee estimation (satoshis per kilobyte) +Transaction.FEE_PER_KB = 100000; + +// Safe upper bound for change address script size in bytes +Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4; +Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4; + +/* Constructors and Serialization */ + +/** + * Create a 'shallow' copy of the transaction, by serializing and deserializing + * it dropping any additional information that inputs and outputs may have hold + * + * @param {Transaction} transaction + * @return {Transaction} + */ +Transaction.shallowCopy = function (transaction) { + var copy = new Transaction(transaction.toBuffer()); + return copy; +}; + +var hashProperty = { + configurable: false, + enumerable: true, + get: function () { + this._hash = new BufferReader(this._getHash()).readReverse().toString('hex'); + return this._hash; + } +}; + +var witnessHashProperty = { + configurable: false, + enumerable: true, + get: function () { + return new BufferReader(this._getWitnessHash()).readReverse().toString('hex'); + } +}; + +Object.defineProperty(Transaction.prototype, 'witnessHash', witnessHashProperty); +Object.defineProperty(Transaction.prototype, 'hash', hashProperty); +Object.defineProperty(Transaction.prototype, 'id', hashProperty); + +var ioProperty = { + configurable: false, + enumerable: true, + get: function () { + return this._getInputAmount(); + } +}; +Object.defineProperty(Transaction.prototype, 'inputAmount', ioProperty); +ioProperty.get = function () { + return this._getOutputAmount(); +}; +Object.defineProperty(Transaction.prototype, 'outputAmount', ioProperty); + +/** + * Retrieve the little endian hash of the transaction (used for serialization) + * @return {Buffer} + */ +Transaction.prototype._getHash = function () { + return Hash.sha256sha256(this.toBuffer(true)); +}; + +/** + * Retrieve the little endian hash of the transaction including witness data + * @return {Buffer} + */ +Transaction.prototype._getWitnessHash = function () { + return Hash.sha256sha256(this.toBuffer(false)); +}; + +/** + * Retrieve a hexa string that can be used with bitcoind's CLI interface + * (decoderawtransaction, sendrawtransaction) + * + * @param {Object|boolean=} unsafe if true, skip all tests. if it's an object, + * it's expected to contain a set of flags to skip certain tests: + * * `disableAll`: disable all checks + * * `disableSmallFees`: disable checking for fees that are too small + * * `disableLargeFees`: disable checking for fees that are too large + * * `disableIsFullySigned`: disable checking if all inputs are fully signed + * * `disableDustOutputs`: disable checking if there are no outputs that are dust amounts + * * `disableMoreOutputThanInput`: disable checking if the transaction spends more bitcoins than the sum of the input amounts + * @return {string} + */ +Transaction.prototype.serialize = function (unsafe) { + if (true === unsafe || unsafe && unsafe.disableAll) { + return this.uncheckedSerialize(); + } else { + return this.checkedSerialize(unsafe); + } +}; + +Transaction.prototype.uncheckedSerialize = Transaction.prototype.toString = function () { + return this.toBuffer().toString('hex'); +}; + +/** + * Retrieve a hexa string that can be used with bitcoind's CLI interface + * (decoderawtransaction, sendrawtransaction) + * + * @param {Object} opts allows to skip certain tests. {@see Transaction#serialize} + * @return {string} + */ +Transaction.prototype.checkedSerialize = function (opts) { + var serializationError = this.getSerializationError(opts); + if (serializationError) { + serializationError.message += ' - For more information please see: ' + + 'https://bitcore.io/api/lib/transaction#serialization-checks'; + throw serializationError; + } + return this.uncheckedSerialize(); +}; + +Transaction.prototype.invalidSatoshis = function () { + var invalid = false; + for (var i = 0; i < this.outputs.length; i++) { + if (this.outputs[i].invalidSatoshis()) { + invalid = true; + } + } + return invalid; +}; + +/** + * Retrieve a possible error that could appear when trying to serialize and + * broadcast this transaction. + * + * @param {Object} opts allows to skip certain tests. {@see Transaction#serialize} + * @return {bitcore.Error} + */ +Transaction.prototype.getSerializationError = function (opts) { + opts = opts || {}; + + if (this.invalidSatoshis()) { + return new errors.Transaction.InvalidSatoshis(); + } + + var unspent = this._getUnspentValue(); + var unspentError; + if (unspent < 0) { + if (!opts.disableMoreOutputThanInput) { + unspentError = new errors.Transaction.InvalidOutputAmountSum(); + } + } else { + unspentError = this._hasFeeError(opts, unspent); + } + + return unspentError || + this._hasDustOutputs(opts) || + this._isMissingSignatures(opts); +}; + +Transaction.prototype._hasFeeError = function (opts, unspent) { + + if (!_.isUndefined(this._fee) && this._fee !== unspent) { + return new errors.Transaction.FeeError.Different( + 'Unspent value is ' + unspent + ' but specified fee is ' + this._fee + ); + } + + if (!opts.disableLargeFees) { + var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee()); + if (unspent > maximumFee) { + if (this._missingChange()) { + return new errors.Transaction.ChangeAddressMissing( + 'Fee is too large and no change address was provided' + ); + } + return new errors.Transaction.FeeError.TooLarge( + 'expected less than ' + maximumFee + ' but got ' + unspent + ); + } + } + + if (!opts.disableSmallFees) { + var minimumFee = Math.ceil(this._estimateFee() / Transaction.FEE_SECURITY_MARGIN); + if (unspent < minimumFee) { + return new errors.Transaction.FeeError.TooSmall( + 'expected more than ' + minimumFee + ' but got ' + unspent + ); + } + } +}; + +Transaction.prototype._missingChange = function () { + return !this._changeScript; +}; + +Transaction.prototype._hasDustOutputs = function (opts) { + if (opts.disableDustOutputs) { + return; + } + var index, output; + for (index in this.outputs) { + output = this.outputs[index]; + if (output.satoshis < Transaction.DUST_AMOUNT && !output.script.isDataOut()) { + return new errors.Transaction.DustOutputs(); + } + } +}; + +Transaction.prototype._isMissingSignatures = function (opts) { + if (opts.disableIsFullySigned) { + return; + } + if (!this.isFullySigned()) { + return new errors.Transaction.MissingSignatures(); + } +}; + +Transaction.prototype.inspect = function () { + return ''; +}; + +Transaction.prototype.toBuffer = function (noWitness) { + var writer = new BufferWriter(); + return this.toBufferWriter(writer, noWitness).toBuffer(); +}; + +Transaction.prototype.hasWitnesses = function () { + for (var i = 0; i < this.inputs.length; i++) { + if (this.inputs[i].hasWitnesses()) { + return true; + } + } + return false; +}; + +Transaction.prototype.toBufferWriter = function (writer, noWitness) { + writer.writeInt32LE(this.version); + + var hasWitnesses = this.hasWitnesses(); + + if (hasWitnesses && !noWitness) { + writer.write(new Buffer('0001', 'hex')); + } + + writer.writeVarintNum(this.inputs.length); + + _.each(this.inputs, function (input) { + input.toBufferWriter(writer); + }); + + writer.writeVarintNum(this.outputs.length); + _.each(this.outputs, function (output) { + output.toBufferWriter(writer); + }); + + if (hasWitnesses && !noWitness) { + _.each(this.inputs, function (input) { + var witnesses = input.getWitnesses(); + writer.writeVarintNum(witnesses.length); + for (var j = 0; j < witnesses.length; j++) { + writer.writeVarintNum(witnesses[j].length); + writer.write(witnesses[j]); + } + }); + } + + writer.writeUInt32LE(this.nLockTime); + return writer; +}; + +Transaction.prototype.fromBuffer = function (buffer) { + var reader = new BufferReader(buffer); + return this.fromBufferReader(reader); +}; + +Transaction.prototype.fromBufferReader = function (reader) { + $.checkArgument(!reader.finished(), 'No transaction data received'); + + this.version = reader.readInt32LE(); + var sizeTxIns = reader.readVarintNum(); + + // check for segwit + var hasWitnesses = false; + if (sizeTxIns === 0 && reader.buf[reader.pos] !== 0) { + reader.pos += 1; + hasWitnesses = true; + sizeTxIns = reader.readVarintNum(); + } + + for (var i = 0; i < sizeTxIns; i++) { + var input = Input.fromBufferReader(reader); + this.inputs.push(input); + } + + var sizeTxOuts = reader.readVarintNum(); + for (var j = 0; j < sizeTxOuts; j++) { + this.outputs.push(Output.fromBufferReader(reader)); + } + + if (hasWitnesses) { + for (var k = 0; k < sizeTxIns; k++) { + var itemCount = reader.readVarintNum(); + var witnesses = []; + for (var l = 0; l < itemCount; l++) { + var size = reader.readVarintNum(); + var item = reader.read(size); + witnesses.push(item); + } + this.inputs[k].setWitnesses(witnesses); + } + } + + this.nLockTime = reader.readUInt32LE(); + return this; +}; + + +Transaction.prototype.toObject = Transaction.prototype.toJSON = function toObject() { + var inputs = []; + this.inputs.forEach(function (input) { + inputs.push(input.toObject()); + }); + var outputs = []; + this.outputs.forEach(function (output) { + outputs.push(output.toObject()); + }); + var obj = { + hash: this.hash, + version: this.version, + inputs: inputs, + outputs: outputs, + nLockTime: this.nLockTime + }; + if (this._changeScript) { + obj.changeScript = this._changeScript.toString(); + } + if (!_.isUndefined(this._changeIndex)) { + obj.changeIndex = this._changeIndex; + } + if (!_.isUndefined(this._fee)) { + obj.fee = this._fee; + } + return obj; +}; + +Transaction.prototype.fromObject = function fromObject(arg) { + /* jshint maxstatements: 20 */ + $.checkArgument(_.isObject(arg) || arg instanceof Transaction); + var self = this; + var transaction; + if (arg instanceof Transaction) { + transaction = transaction.toObject(); + } else { + transaction = arg; + } + _.each(transaction.inputs, function (input) { + if (!input.output || !input.output.script) { + self.uncheckedAddInput(new Input(input)); + return; + } + var script = new Script(input.output.script); + var txin; + if (script.isPublicKeyHashOut()) { + txin = new Input.PublicKeyHash(input); + } else if (script.isScriptHashOut() && input.publicKeys && input.threshold) { + txin = new Input.MultiSigScriptHash( + input, input.publicKeys, input.threshold, input.signatures + ); + } else if (script.isPublicKeyOut()) { + txin = new Input.PublicKey(input); + } else { + throw new errors.Transaction.Input.UnsupportedScript(input.output.script); + } + self.addInput(txin); + }); + _.each(transaction.outputs, function (output) { + self.addOutput(new Output(output)); + }); + if (transaction.changeIndex) { + this._changeIndex = transaction.changeIndex; + } + if (transaction.changeScript) { + this._changeScript = new Script(transaction.changeScript); + } + if (transaction.fee) { + this._fee = transaction.fee; + } + this.nLockTime = transaction.nLockTime; + this.version = transaction.version; + this._checkConsistency(arg); + return this; +}; + +Transaction.prototype._checkConsistency = function (arg) { + if (!_.isUndefined(this._changeIndex)) { + $.checkState(this._changeScript, 'Change script is expected.'); + $.checkState(this.outputs[this._changeIndex], 'Change index points to undefined output.'); + $.checkState(this.outputs[this._changeIndex].script.toString() === + this._changeScript.toString(), 'Change output has an unexpected script.'); + } + if (arg && arg.hash) { + $.checkState(arg.hash === this.hash, 'Hash in object does not match transaction hash.'); + } +}; + +/** + * Sets nLockTime so that transaction is not valid until the desired date(a + * timestamp in seconds since UNIX epoch is also accepted) + * + * @param {Date | Number} time + * @return {Transaction} this + */ +Transaction.prototype.lockUntilDate = function (time) { + $.checkArgument(time); + if (_.isNumber(time) && time < Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT) { + throw new errors.Transaction.LockTimeTooEarly(); + } + if (_.isDate(time)) { + time = time.getTime() / 1000; + } + + for (var i = 0; i < this.inputs.length; i++) { + if (this.inputs[i].sequenceNumber === Input.DEFAULT_SEQNUMBER) { + this.inputs[i].sequenceNumber = Input.DEFAULT_LOCKTIME_SEQNUMBER; + } + } + + this.nLockTime = time; + return this; +}; + +/** + * Sets nLockTime so that transaction is not valid until the desired block + * height. + * + * @param {Number} height + * @return {Transaction} this + */ +Transaction.prototype.lockUntilBlockHeight = function (height) { + $.checkArgument(_.isNumber(height)); + if (height >= Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT) { + throw new errors.Transaction.BlockHeightTooHigh(); + } + if (height < 0) { + throw new errors.Transaction.NLockTimeOutOfRange(); + } + + for (var i = 0; i < this.inputs.length; i++) { + if (this.inputs[i].sequenceNumber === Input.DEFAULT_SEQNUMBER) { + this.inputs[i].sequenceNumber = Input.DEFAULT_LOCKTIME_SEQNUMBER; + } + } + + + this.nLockTime = height; + return this; +}; + +/** + * Returns a semantic version of the transaction's nLockTime. + * @return {Number|Date} + * If nLockTime is 0, it returns null, + * if it is < 500000000, it returns a block height (number) + * else it returns a Date object. + */ +Transaction.prototype.getLockTime = function () { + if (!this.nLockTime) { + return null; + } + if (this.nLockTime < Transaction.NLOCKTIME_BLOCKHEIGHT_LIMIT) { + return this.nLockTime; + } + return new Date(1000 * this.nLockTime); +}; + +Transaction.prototype.fromString = function (string) { + this.fromBuffer(buffer.Buffer.from(string, 'hex')); +}; + +Transaction.prototype._newTransaction = function () { + this.version = CURRENT_VERSION; + this.nLockTime = DEFAULT_NLOCKTIME; +}; + +/* Transaction creation interface */ + +/** + * @typedef {Object} Transaction~fromObject + * @property {string} prevTxId + * @property {number} outputIndex + * @property {(Buffer|string|Script)} script + * @property {number} satoshis + */ + +/** + * Add an input to this transaction. This is a high level interface + * to add an input, for more control, use @{link Transaction#addInput}. + * + * Can receive, as output information, the output of bitcoind's `listunspent` command, + * and a slightly fancier format recognized by bitcore: + * + * ``` + * { + * address: 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1', + * txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', + * outputIndex: 0, + * script: Script.empty(), + * satoshis: 1020000 + * } + * ``` + * Where `address` can be either a string or a bitcore Address object. The + * same is true for `script`, which can be a string or a bitcore Script. + * + * Beware that this resets all the signatures for inputs (in further versions, + * SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). + * + * @example + * ```javascript + * var transaction = new Transaction(); + * + * // From a pay to public key hash output from bitcoind's listunspent + * transaction.from({'txid': '0000...', vout: 0, amount: 0.1, scriptPubKey: 'OP_DUP ...'}); + * + * // From a pay to public key hash output + * transaction.from({'txId': '0000...', outputIndex: 0, satoshis: 1000, script: 'OP_DUP ...'}); + * + * // From a multisig P2SH output + * transaction.from({'txId': '0000...', inputIndex: 0, satoshis: 1000, script: '... OP_HASH'}, + * ['03000...', '02000...'], 2); + * ``` + * + * @param {(Array.|Transaction~fromObject)} utxo + * @param {Array=} pubkeys + * @param {number=} threshold + * @param {boolean=} nestedWitness - Indicates that the utxo is nested witness p2sh + */ +Transaction.prototype.from = function (utxo, pubkeys, threshold, nestedWitness) { + if (_.isArray(utxo)) { + var self = this; + _.each(utxo, function (utxo) { + self.from(utxo, pubkeys, threshold); + }); + return this; + } + var exists = _.some(this.inputs, function (input) { + // TODO: Maybe prevTxId should be a string? Or defined as read only property? + return input.prevTxId.toString('hex') === utxo.txId && input.outputIndex === utxo.outputIndex; + }); + if (exists) { + return this; + } + if (pubkeys && threshold) { + this._fromMultisigUtxo(utxo, pubkeys, threshold, nestedWitness); + } else { + this._fromNonP2SH(utxo); + } + return this; +}; + +Transaction.prototype._fromNonP2SH = function (utxo) { + var clazz; + utxo = new UnspentOutput(utxo); + if (utxo.script.isPublicKeyHashOut()) { + clazz = PublicKeyHashInput; + } else if (utxo.script.isPublicKeyOut()) { + clazz = PublicKeyInput; + } else { + clazz = Input; + } + this.addInput(new clazz({ + output: new Output({ + script: utxo.script, + satoshis: utxo.satoshis + }), + prevTxId: utxo.txId, + outputIndex: utxo.outputIndex, + script: Script.empty() + })); +}; + +Transaction.prototype._fromMultisigUtxo = function (utxo, pubkeys, threshold, nestedWitness) { + $.checkArgument(threshold <= pubkeys.length, + 'Number of required signatures must be greater than the number of public keys'); + var clazz; + utxo = new UnspentOutput(utxo); + if (utxo.script.isMultisigOut()) { + clazz = MultiSigInput; + } else if (utxo.script.isScriptHashOut()) { + clazz = MultiSigScriptHashInput; + } else { + throw new Error("@TODO"); + } + this.addInput(new clazz({ + output: new Output({ + script: utxo.script, + satoshis: utxo.satoshis + }), + prevTxId: utxo.txId, + outputIndex: utxo.outputIndex, + script: Script.empty() + }, pubkeys, threshold, false, nestedWitness)); +}; + +/** + * Add an input to this transaction. The input must be an instance of the `Input` class. + * It should have information about the Output that it's spending, but if it's not already + * set, two additional parameters, `outputScript` and `satoshis` can be provided. + * + * @param {Input} input + * @param {String|Script} outputScript + * @param {number} satoshis + * @return Transaction this, for chaining + */ +Transaction.prototype.addInput = function (input, outputScript, satoshis) { + $.checkArgumentType(input, Input, 'input'); + if (!input.output && (_.isUndefined(outputScript) || _.isUndefined(satoshis))) { + throw new errors.Transaction.NeedMoreInfo('Need information about the UTXO script and satoshis'); + } + if (!input.output && outputScript && !_.isUndefined(satoshis)) { + outputScript = outputScript instanceof Script ? outputScript : new Script(outputScript); + $.checkArgumentType(satoshis, 'number', 'satoshis'); + input.output = new Output({ + script: outputScript, + satoshis: satoshis + }); + } + return this.uncheckedAddInput(input); +}; + +/** + * Add an input to this transaction, without checking that the input has information about + * the output that it's spending. + * + * @param {Input} input + * @return Transaction this, for chaining + */ +Transaction.prototype.uncheckedAddInput = function (input) { + $.checkArgumentType(input, Input, 'input'); + this.inputs.push(input); + this._inputAmount = undefined; + this._updateChangeOutput(); + return this; +}; + +/** + * Returns true if the transaction has enough info on all inputs to be correctly validated + * + * @return {boolean} + */ +Transaction.prototype.hasAllUtxoInfo = function () { + return _.every(this.inputs.map(function (input) { + return !!input.output; + })); +}; + +/** + * Manually set the fee for this transaction. Beware that this resets all the signatures + * for inputs (in further versions, SIGHASH_SINGLE or SIGHASH_NONE signatures will not + * be reset). + * + * @param {number} amount satoshis to be sent + * @return {Transaction} this, for chaining + */ +Transaction.prototype.fee = function (amount) { + $.checkArgument(_.isNumber(amount), 'amount must be a number'); + this._fee = amount; + this._updateChangeOutput(); + return this; +}; + +/** + * Manually set the fee per KB for this transaction. Beware that this resets all the signatures + * for inputs (in further versions, SIGHASH_SINGLE or SIGHASH_NONE signatures will not + * be reset). + * + * @param {number} amount satoshis per KB to be sent + * @return {Transaction} this, for chaining + */ +Transaction.prototype.feePerKb = function (amount) { + $.checkArgument(_.isNumber(amount), 'amount must be a number'); + this._feePerKb = amount; + this._updateChangeOutput(); + return this; +}; + +/* Output management */ + +/** + * Set the change address for this transaction + * + * Beware that this resets all the signatures for inputs (in further versions, + * SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). + * + * @param {Address} address An address for change to be sent to. + * @return {Transaction} this, for chaining + */ +Transaction.prototype.change = function (address) { + $.checkArgument(address, 'address is required'); + this._changeScript = Script.fromAddress(address); + this._updateChangeOutput(); + return this; +}; + + +/** + * @return {Output} change output, if it exists + */ +Transaction.prototype.getChangeOutput = function () { + if (!_.isUndefined(this._changeIndex)) { + return this.outputs[this._changeIndex]; + } + return null; +}; + +/** + * @typedef {Object} Transaction~toObject + * @property {(string|Address)} address + * @property {number} satoshis + */ + +/** + * Add an output to the transaction. + * + * Beware that this resets all the signatures for inputs (in further versions, + * SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). + * + * @param {(string|Address|Array.)} address + * @param {number} amount in satoshis + * @return {Transaction} this, for chaining + */ +Transaction.prototype.to = function (address, amount) { + if (_.isArray(address)) { + var self = this; + _.each(address, function (to) { + self.to(to.address, to.satoshis); + }); + return this; + } + + $.checkArgument( + JSUtil.isNaturalNumber(amount), + 'Amount is expected to be a positive integer' + ); + this.addOutput(new Output({ + script: Script(new Address(address)), + satoshis: amount + })); + return this; +}; + +/** + * Add an OP_RETURN output to the transaction. + * + * Beware that this resets all the signatures for inputs (in further versions, + * SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). + * + * @param {Buffer|string} value the data to be stored in the OP_RETURN output. + * In case of a string, the UTF-8 representation will be stored + * @return {Transaction} this, for chaining + */ +Transaction.prototype.addData = function (value) { + this.addOutput(new Output({ + script: Script.buildDataOut(value), + satoshis: 0 + })); + return this; +}; + + +/** + * Add an output to the transaction. + * + * @param {Output} output the output to add. + * @return {Transaction} this, for chaining + */ +Transaction.prototype.addOutput = function (output) { + $.checkArgumentType(output, Output, 'output'); + this._addOutput(output); + this._updateChangeOutput(); + return this; +}; + + +/** + * Remove all outputs from the transaction. + * + * @return {Transaction} this, for chaining + */ +Transaction.prototype.clearOutputs = function () { + this.outputs = []; + this._clearSignatures(); + this._outputAmount = undefined; + this._changeIndex = undefined; + this._updateChangeOutput(); + return this; +}; + + +Transaction.prototype._addOutput = function (output) { + this.outputs.push(output); + this._outputAmount = undefined; +}; + + +/** + * Calculates or gets the total output amount in satoshis + * + * @return {Number} the transaction total output amount + */ +Transaction.prototype._getOutputAmount = function () { + if (_.isUndefined(this._outputAmount)) { + var self = this; + this._outputAmount = 0; + _.each(this.outputs, function (output) { + self._outputAmount += output.satoshis; + }); + } + return this._outputAmount; +}; + + +/** + * Calculates or gets the total input amount in satoshis + * + * @return {Number} the transaction total input amount + */ +Transaction.prototype._getInputAmount = function () { + if (_.isUndefined(this._inputAmount)) { + var self = this; + this._inputAmount = 0; + _.each(this.inputs, function (input) { + if (_.isUndefined(input.output)) { + throw new errors.Transaction.Input.MissingPreviousOutput(); + } + self._inputAmount += input.output.satoshis; + }); + } + return this._inputAmount; +}; + +Transaction.prototype._updateChangeOutput = function () { + if (!this._changeScript) { + return; + } + this._clearSignatures(); + if (!_.isUndefined(this._changeIndex)) { + this._removeOutput(this._changeIndex); + } + var available = this._getUnspentValue(); + var fee = this.getFee(); + var changeAmount = available - fee; + if (changeAmount > 0) { + this._changeIndex = this.outputs.length; + this._addOutput(new Output({ + script: this._changeScript, + satoshis: changeAmount + })); + } else { + this._changeIndex = undefined; + } +}; +/** + * Calculates the fee of the transaction. + * + * If there's a fixed fee set, return that. + * + * If there is no change output set, the fee is the + * total value of the outputs minus inputs. Note that + * a serialized transaction only specifies the value + * of its outputs. (The value of inputs are recorded + * in the previous transaction outputs being spent.) + * This method therefore raises a "MissingPreviousOutput" + * error when called on a serialized transaction. + * + * If there's no fee set and no change address, + * estimate the fee based on size. + * + * @return {Number} fee of this transaction in satoshis + */ +Transaction.prototype.getFee = function () { + if (this.isCoinbase()) { + return 0; + } + if (!_.isUndefined(this._fee)) { + return this._fee; + } + // if no change output is set, fees should equal all the unspent amount + if (!this._changeScript) { + return this._getUnspentValue(); + } + return this._estimateFee(); +}; + +/** + * Estimates fee from serialized transaction size in bytes. + */ +Transaction.prototype._estimateFee = function () { + var estimatedSize = this._estimateSize(); + var available = this._getUnspentValue(); + return Transaction._estimateFee(estimatedSize, available, this._feePerKb); +}; + +Transaction.prototype._getUnspentValue = function () { + return this._getInputAmount() - this._getOutputAmount(); +}; + +Transaction.prototype._clearSignatures = function () { + _.each(this.inputs, function (input) { + input.clearSignatures(); + }); +}; + +Transaction._estimateFee = function (size, amountAvailable, feePerKb) { + var fee = Math.ceil(size / 1000) * (feePerKb || Transaction.FEE_PER_KB); + if (amountAvailable > fee) { + size += Transaction.CHANGE_OUTPUT_MAX_SIZE; + } + return Math.ceil(size / 1000) * (feePerKb || Transaction.FEE_PER_KB); +}; + +Transaction.prototype._estimateSize = function () { + var result = Transaction.MAXIMUM_EXTRA_SIZE; + _.each(this.inputs, function (input) { + result += input._estimateSize(); + }); + _.each(this.outputs, function (output) { + result += output.script.toBuffer().length + 9; + }); + return result; +}; + +Transaction.prototype._removeOutput = function (index) { + var output = this.outputs[index]; + this.outputs = _.without(this.outputs, output); + this._outputAmount = undefined; +}; + +Transaction.prototype.removeOutput = function (index) { + this._removeOutput(index); + this._updateChangeOutput(); +}; + +/** + * Sort a transaction's inputs and outputs according to BIP69 + * + * @see {https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki} + * @return {Transaction} this + */ +Transaction.prototype.sort = function () { + this.sortInputs(function (inputs) { + var copy = Array.prototype.concat.apply([], inputs); + copy.sort(function (first, second) { + return compare(first.prevTxId, second.prevTxId) + || first.outputIndex - second.outputIndex; + }); + return copy; + }); + this.sortOutputs(function (outputs) { + var copy = Array.prototype.concat.apply([], outputs); + copy.sort(function (first, second) { + return first.satoshis - second.satoshis + || compare(first.script.toBuffer(), second.script.toBuffer()); + }); + return copy; + }); + return this; +}; + +/** + * Randomize this transaction's outputs ordering. The shuffling algorithm is a + * version of the Fisher-Yates shuffle, provided by lodash's _.shuffle(). + * + * @return {Transaction} this + */ +Transaction.prototype.shuffleOutputs = function () { + return this.sortOutputs(_.shuffle); +}; + +/** + * Sort this transaction's outputs, according to a given sorting function that + * takes an array as argument and returns a new array, with the same elements + * but with a different order. The argument function MUST NOT modify the order + * of the original array + * + * @param {Function} sortingFunction + * @return {Transaction} this + */ +Transaction.prototype.sortOutputs = function (sortingFunction) { + var outs = sortingFunction(this.outputs); + return this._newOutputOrder(outs); +}; + +/** + * Sort this transaction's inputs, according to a given sorting function that + * takes an array as argument and returns a new array, with the same elements + * but with a different order. + * + * @param {Function} sortingFunction + * @return {Transaction} this + */ +Transaction.prototype.sortInputs = function (sortingFunction) { + this.inputs = sortingFunction(this.inputs); + this._clearSignatures(); + return this; +}; + +Transaction.prototype._newOutputOrder = function (newOutputs) { + var isInvalidSorting = (this.outputs.length !== newOutputs.length || + _.difference(this.outputs, newOutputs).length !== 0); + if (isInvalidSorting) { + throw new errors.Transaction.InvalidSorting(); + } + + if (!_.isUndefined(this._changeIndex)) { + var changeOutput = this.outputs[this._changeIndex]; + this._changeIndex = _.findIndex(newOutputs, changeOutput); + } + + this.outputs = newOutputs; + return this; +}; + +Transaction.prototype.removeInput = function (txId, outputIndex) { + var index; + if (!outputIndex && _.isNumber(txId)) { + index = txId; + } else { + index = _.findIndex(this.inputs, function (input) { + return input.prevTxId.toString('hex') === txId && input.outputIndex === outputIndex; + }); + } + if (index < 0 || index >= this.inputs.length) { + throw new errors.Transaction.InvalidIndex(index, this.inputs.length); + } + var input = this.inputs[index]; + this.inputs = _.without(this.inputs, input); + this._inputAmount = undefined; + this._updateChangeOutput(); +}; + +/* Signature handling */ + +/** + * Sign the transaction using one or more private keys. + * + * It tries to sign each input, verifying that the signature will be valid + * (matches a public key). + * + * @param {Array|String|PrivateKey} privateKey + * @param {number} sigtype + * @return {Transaction} this, for chaining + */ +Transaction.prototype.sign = function (privateKey, sigtype) { + $.checkState(this.hasAllUtxoInfo(), 'Not all utxo information is available to sign the transaction.'); + var self = this; + if (_.isArray(privateKey)) { + _.each(privateKey, function (privateKey) { + self.sign(privateKey, sigtype); + }); + return this; + } + _.each(this.getSignatures(privateKey, sigtype), function (signature) { + self.applySignature(signature); + }); + return this; +}; + +Transaction.prototype.getSignatures = function (privKey, sigtype) { + privKey = new PrivateKey(privKey); + sigtype = sigtype || Signature.SIGHASH_ALL; + var transaction = this; + var results = []; + var hashData = Hash.sha256ripemd160(privKey.publicKey.toBuffer()); + _.each(this.inputs, function forEachInput(input, index) { + _.each(input.getSignatures(transaction, privKey, index, sigtype, hashData), function (signature) { + results.push(signature); + }); + }); + return results; +}; + +/** + * Add a signature to the transaction + * + * @param {Object} signature + * @param {number} signature.inputIndex + * @param {number} signature.sigtype + * @param {PublicKey} signature.publicKey + * @param {Signature} signature.signature + * @return {Transaction} this, for chaining + */ +Transaction.prototype.applySignature = function (signature) { + this.inputs[signature.inputIndex].addSignature(this, signature); + return this; +}; + +Transaction.prototype.isFullySigned = function () { + _.each(this.inputs, function (input) { + if (input.isFullySigned === Input.prototype.isFullySigned) { + throw new errors.Transaction.UnableToVerifySignature( + 'Unrecognized script kind, or not enough information to execute script.' + + 'This usually happens when creating a transaction from a serialized transaction' + ); + } + }); + return _.every(_.map(this.inputs, function (input) { + return input.isFullySigned(); + })); +}; + +Transaction.prototype.isValidSignature = function (signature) { + var self = this; + if (this.inputs[signature.inputIndex].isValidSignature === Input.prototype.isValidSignature) { + throw new errors.Transaction.UnableToVerifySignature( + 'Unrecognized script kind, or not enough information to execute script.' + + 'This usually happens when creating a transaction from a serialized transaction' + ); + } + return this.inputs[signature.inputIndex].isValidSignature(self, signature); +}; + +/** + * @returns {bool} whether the signature is valid for this transaction input + */ +Transaction.prototype.verifySignature = function (sig, pubkey, nin, subscript, sigversion, satoshis) { + + if (_.isUndefined(sigversion)) { + sigversion = 0; + } + + if (sigversion === 1) { + var subscriptBuffer = subscript.toBuffer(); + var scriptCodeWriter = new BufferWriter(); + scriptCodeWriter.writeVarintNum(subscriptBuffer.length); + scriptCodeWriter.write(subscriptBuffer); + + var satoshisBuffer; + if (satoshis) { + $.checkState(JSUtil.isNaturalNumber(satoshis)); + satoshisBuffer = new BufferWriter().writeUInt64LEBN(new BN(satoshis)).toBuffer(); + } else { + satoshisBuffer = this.inputs[nin].getSatoshisBuffer(); + } + var verified = SighashWitness.verify( + this, + sig, + pubkey, + nin, + scriptCodeWriter.toBuffer(), + satoshisBuffer + ); + return verified; + } + + return Sighash.verify(this, sig, pubkey, nin, subscript); +}; + +/** + * Check that a transaction passes basic sanity tests. If not, return a string + * describing the error. This function contains the same logic as + * CheckTransaction in bitcoin core. + */ +Transaction.prototype.verify = function () { + // Basic checks that don't depend on any context + if (this.inputs.length === 0) { + return 'transaction txins empty'; + } + + if (this.outputs.length === 0) { + return 'transaction txouts empty'; + } + + // Check for negative or overflow output values + var valueoutbn = new BN(0); + for (var i = 0; i < this.outputs.length; i++) { + var txout = this.outputs[i]; + + if (txout.invalidSatoshis()) { + return 'transaction txout ' + i + ' satoshis is invalid'; + } + if (txout._satoshisBN.gt(new BN(Transaction.MAX_MONEY, 10))) { + return 'transaction txout ' + i + ' greater than MAX_MONEY'; + } + valueoutbn = valueoutbn.add(txout._satoshisBN); + if (valueoutbn.gt(new BN(Transaction.MAX_MONEY))) { + return 'transaction txout ' + i + ' total output greater than MAX_MONEY'; + } + } + + // Size limits + if (this.toBuffer().length > MAX_BLOCK_SIZE) { + return 'transaction over the maximum block size'; + } + + // Check for duplicate inputs + var txinmap = {}; + for (i = 0; i < this.inputs.length; i++) { + var txin = this.inputs[i]; + + var inputid = txin.prevTxId + ':' + txin.outputIndex; + if (!_.isUndefined(txinmap[inputid])) { + return 'transaction input ' + i + ' duplicate input'; + } + txinmap[inputid] = true; + } + + var isCoinbase = this.isCoinbase(); + if (isCoinbase) { + var buf = this.inputs[0]._scriptBuffer; + if (buf.length < 2 || buf.length > 100) { + return 'coinbase transaction script size invalid'; + } + } else { + for (i = 0; i < this.inputs.length; i++) { + if (this.inputs[i].isNull()) { + return 'transaction input ' + i + ' has null input'; + } + } + } + return true; +}; + +/** + * Analogous to bitcoind's IsCoinBase function in transaction.h + */ +Transaction.prototype.isCoinbase = function () { + return (this.inputs.length === 1 && this.inputs[0].isNull()); +}; + +/** + * Determines if this transaction can be replaced in the mempool with another + * transaction that provides a sufficiently higher fee (RBF). + */ +Transaction.prototype.isRBF = function () { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + if (input.sequenceNumber < Input.MAXINT - 1) { + return true; + } + } + return false; +}; + +/** + * Enable this transaction to be replaced in the mempool (RBF) if a transaction + * includes a sufficiently higher fee. It will set the sequenceNumber to + * DEFAULT_RBF_SEQNUMBER for all inputs if the sequence number does not + * already enable RBF. + */ +Transaction.prototype.enableRBF = function () { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + if (input.sequenceNumber >= Input.MAXINT - 1) { + input.sequenceNumber = Input.DEFAULT_RBF_SEQNUMBER; + } + } + return this; +}; + +export default Transaction; diff --git a/src/backend/wallet/lib/transaction/ucointransfertx.js b/src/backend/wallet/lib/transaction/ucointransfertx.js new file mode 100644 index 0000000..4b01d03 --- /dev/null +++ b/src/backend/wallet/lib/transaction/ucointransfertx.js @@ -0,0 +1,184 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import BufferWriter from '../encoding/bufferwriter'; +import bufferUtil from '../util/buffer'; +import WriterHelper from '../util/writerhelper'; + +var UCoinTransferTx = function UCoinTransferTx(arg) { + if (!(this instanceof UCoinTransferTx)) { + return new UCoinTransferTx(arg); + } + var info = UCoinTransferTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.srcRegId = info.srcRegId; + this.destArr = info.destArr; + this.fees = info.fees; + this.publicKey = info.publicKey; + this.feesCoinType = info.feesCoinType; + this.memo = info.memo; + this.network = info.network; + + return this; +}; + +UCoinTransferTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = UCoinTransferTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for UCoinTransferTx'); + } + return info; +}; + +UCoinTransferTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + srcRegId: data.srcRegId, + destArr: data.destArr, + fees: data.fees, + publicKey: data.publicKey, + feesCoinType: data.feesCoinType, + memo: data.memo + }; + return info; +}; + +UCoinTransferTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarintNum(this.nVersion) + writer.writeVarintNum(this.nTxType) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feesCoinType == null || _.isEmpty(this.feesCoinType)) return fasle; + writer.writeString(this.feesCoinType) + + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + writer.writeDestArrData(this.destArr) + var len = 0 + var buf = this.memo + if (!_.isEmpty(this.memo)) { + if (!bufferUtil.isBuffer(buf)) { + buf = Buffer.from(buf) + } + len = buf.length + } + writer.writeVarintNum(len) + if (len > 0) { + writer.write(buf) + } + + var serialBuf = writer.toBuffer() + + return Hash.sha256sha256(serialBuf); +} + +UCoinTransferTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +UCoinTransferTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeVarintNum(this.nTxType) + writer.writeVarintNum(this.nVersion) + var heightBuf = Util.writeVarInt(4, this.nValidHeight) + writer.write(heightBuf) + + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + var REGID = Util.splitRegID(this.srcRegId) + if (_.isNull(REGID.height) || _.isUndefined(REGID.height)) + return false + var regWriter = new BufferWriter() + var regHeightBuf = Util.writeVarInt(4, REGID.height) + regWriter.write(regHeightBuf) + var regIndexBuf = Util.writeVarInt(2, REGID.index) + regWriter.write(regIndexBuf) + + var regBuf = regWriter.toBuffer() + writer.writeUInt8(regBuf.length) + writer.write(regBuf) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + if (this.feesCoinType == null || _.isEmpty(this.feesCoinType)) return fasle; + writer.writeString(this.feesCoinType) + + if (! /^[1-9]*[1-9][0-9]*$/.test(this.fees)) + return false; + var feesBuf = Util.writeVarInt(8, this.fees) + writer.write(feesBuf) + writer.writeDestArrData(this.destArr) + var len = 0 + var buf = this.memo + if (!_.isEmpty(this.memo)) { + if (!bufferUtil.isBuffer(buf)) { + buf = Buffer.from(buf) + } + len = buf.length + } + writer.writeVarintNum(len) + if (len > 0) { + writer.write(buf) + } + + + + var sigBuf = this._Signtx(privateKey) + + var len = sigBuf.length + writer.writeVarintNum(len) + writer.write(sigBuf) + + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default UCoinTransferTx; \ No newline at end of file diff --git a/src/backend/wallet/lib/transaction/ucontractinvoketx.js b/src/backend/wallet/lib/transaction/ucontractinvoketx.js new file mode 100644 index 0000000..498ab94 --- /dev/null +++ b/src/backend/wallet/lib/transaction/ucontractinvoketx.js @@ -0,0 +1,145 @@ +'use strict'; + +import _ from 'lodash'; +import assert from "assert"; +import $ from '../util/preconditions'; +import Util from '../util/util' +import Hash from '../crypto/hash'; +import ECDSA from '../crypto/ecdsa'; +import WriterHelper from '../util/writerhelper'; +import errors from '../errors'; + +var UContractTx = function UContractTx(arg) { + if (!(this instanceof UContractTx)) { + return new UContractTx(arg); + } + var info = UContractTx._from(arg); + this.nTxType = info.nTxType; + this.nVersion = info.nVersion; + this.nValidHeight = info.nValidHeight; + this.srcRegId = info.srcRegId; + this.destRegId = info.destRegId; + this.fees = info.fees; + this.value = info.value; + this.feesCoinType = info.feesCoinType; + this.publicKey = info.publicKey; + this.coinType = info.coinType; + this.vContract = info.vContract; + + return this; +}; + +UContractTx._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = UContractTx._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for ContractTx'); + } + return info; +}; + +UContractTx._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + // if (!Util.checkRegId(data.srcRegId)) { + // throw new errors.InvalidArgument("srcRegId", "Invalid reg id"); + // } + if (!Util.checkRegId(data.destRegId)) { + throw new errors.InvalidArgument("destRegId", "Invalid reg id"); + } + if (!_.isString(data.vContract) || _.isEmpty(data.vContract)) { + throw new errors.InvalidArgument("vContract", "Invalid vContract"); + } + + var contract = Util.hexToBytes(data.vContract) + if (!contract) { + throw new errors.InvalidArgument("vContract", "Invalid vContract"); + } + + var info = { + nTxType: data.nTxType, + nVersion: data.nVersion, + nValidHeight: data.nValidHeight, + srcRegId: data.srcRegId, + destRegId: data.destRegId, + fees: data.fees, + value: data.value, + publicKey: data.publicKey, + coinType: data.coinType, + feesCoinType: data.feesCoinType, + vContract: contract + }; + return info; +}; + +UContractTx.prototype._SignatureHash = function () { + var writer = new WriterHelper(); + writer.writeVarInt(4, this.nVersion) + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + + writer.writeRegId(this.destRegId) + writer.writeString(Buffer.from(this.vContract)) + writer.writeVarInt(8, this.fees) + writer.writeString(Buffer.from(this.feesCoinType)) + writer.writeString(Buffer.from(this.coinType)) + writer.writeVarInt(8, this.value) + + var serialBuf = writer.toBuffer() + + //console.log(serialBuf.toString('hex')) + + return Hash.sha256sha256(serialBuf); +} + +UContractTx.prototype._Signtx = function (privateKey) { + var hashbuf = this._SignatureHash() + var sig = ECDSA.sign(hashbuf, privateKey, 'endian') + var sigBuf = sig.toBuffer() + + return sigBuf; +} + +UContractTx.prototype.SerializeTx = function (privateKey) { + var writer = new WriterHelper(); + writer.writeUInt8(this.nTxType) + writer.writeVarInt(4, this.nVersion) + writer.writeVarInt(4, this.nValidHeight) + + if (this.srcRegId != null && !_.isEmpty(this.srcRegId)) { + writer.writeRegId(this.srcRegId) + } else if (this.publicKey != null && !_.isEmpty(this, this.publicKey)) { + var pubKey = Buffer.from(this.publicKey, 'hex') + writer.writeUInt8(pubKey.length) + writer.write(pubKey) + } else { + return false; + } + writer.writeRegId(this.destRegId) + writer.writeString(Buffer.from(this.vContract)) + writer.writeVarInt(8, this.fees) + writer.writeString(Buffer.from(this.feesCoinType)) + writer.writeString(Buffer.from(this.coinType)) + writer.writeVarInt(8, this.value) + + var sigBuf = this._Signtx(privateKey) + writer.writeBuf(sigBuf) + + var hexBuf = writer.toBuffer() + var hex = hexBuf.toString('hex') + + return hex +} + + +export default UContractTx; diff --git a/src/backend/wallet/lib/transaction/unspentoutput.js b/src/backend/wallet/lib/transaction/unspentoutput.js new file mode 100644 index 0000000..2faf234 --- /dev/null +++ b/src/backend/wallet/lib/transaction/unspentoutput.js @@ -0,0 +1,100 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; +import JSUtil from '../util/js'; + +import Script from '../script'; +import Address from '../address'; +import Unit from '../unit'; + +/** + * Represents an unspent output information: its script, associated amount and address, + * transaction id and output index. + * + * @constructor + * @param {object} data + * @param {string} data.txid the previous transaction id + * @param {string=} data.txId alias for `txid` + * @param {number} data.vout the index in the transaction + * @param {number=} data.outputIndex alias for `vout` + * @param {string|Script} data.scriptPubKey the script that must be resolved to release the funds + * @param {string|Script=} data.script alias for `scriptPubKey` + * @param {number} data.amount amount of bitcoins associated + * @param {number=} data.satoshis alias for `amount`, but expressed in satoshis (1 BTC = 1e8 satoshis) + * @param {string|Address=} data.address the associated address to the script, if provided + */ +function UnspentOutput(data) { + /* jshint maxcomplexity: 20 */ + /* jshint maxstatements: 20 */ + if (!(this instanceof UnspentOutput)) { + return new UnspentOutput(data); + } + $.checkArgument(_.isObject(data), 'Must provide an object from where to extract data'); + var address = data.address ? new Address(data.address) : undefined; + var txId = data.txid ? data.txid : data.txId; + if (!txId || !JSUtil.isHexaString(txId) || txId.length > 64) { + // TODO: Use the errors library + throw new Error('Invalid TXID in object', data); + } + var outputIndex = _.isUndefined(data.vout) ? data.outputIndex : data.vout; + if (!_.isNumber(outputIndex)) { + throw new Error('Invalid outputIndex, received ' + outputIndex); + } + $.checkArgument(!_.isUndefined(data.scriptPubKey) || !_.isUndefined(data.script), + 'Must provide the scriptPubKey for that output!'); + var script = new Script(data.scriptPubKey || data.script); + $.checkArgument(!_.isUndefined(data.amount) || !_.isUndefined(data.satoshis), + 'Must provide an amount for the output'); + var amount = !_.isUndefined(data.amount) ? new Unit.fromBTC(data.amount).toSatoshis() : data.satoshis; + $.checkArgument(_.isNumber(amount), 'Amount must be a number'); + JSUtil.defineImmutable(this, { + address: address, + txId: txId, + outputIndex: outputIndex, + script: script, + satoshis: amount + }); +} + +/** + * Provide an informative output when displaying this object in the console + * @returns string + */ +UnspentOutput.prototype.inspect = function () { + return ''; +}; + +/** + * String representation: just "txid:index" + * @returns string + */ +UnspentOutput.prototype.toString = function () { + return this.txId + ':' + this.outputIndex; +}; + +/** + * Deserialize an UnspentOutput from an object + * @param {object|string} data + * @return UnspentOutput + */ +UnspentOutput.fromObject = function (data) { + return new UnspentOutput(data); +}; + +/** + * Returns a plain object (no prototype or methods) with the associated info for this output + * @return {object} + */ +UnspentOutput.prototype.toObject = UnspentOutput.prototype.toJSON = function toObject() { + return { + address: this.address ? this.address.toString() : undefined, + txid: this.txId, + vout: this.outputIndex, + scriptPubKey: this.script.toBuffer().toString('hex'), + amount: Unit.fromSatoshis(this.satoshis).toBTC() + }; +}; + +export default UnspentOutput; diff --git a/src/backend/wallet/lib/unit.js b/src/backend/wallet/lib/unit.js new file mode 100644 index 0000000..de42461 --- /dev/null +++ b/src/backend/wallet/lib/unit.js @@ -0,0 +1,238 @@ +'use strict'; + +import _ from 'lodash'; + +import errors from './errors'; +import $ from './util/preconditions'; + +var UNITS = { + 'BTC': [1e8, 8], + 'mBTC': [1e5, 5], + 'uBTC': [1e2, 2], + 'bits': [1e2, 2], + 'satoshis': [1, 0] +}; + +/** + * Utility for handling and converting bitcoins units. The supported units are + * BTC, mBTC, bits (also named uBTC) and satoshis. A unit instance can be created with an + * amount and a unit code, or alternatively using static methods like {fromBTC}. + * It also allows to be created from a fiat amount and the exchange rate, or + * alternatively using the {fromFiat} static method. + * You can consult for different representation of a unit instance using it's + * {to} method, the fixed unit methods like {toSatoshis} or alternatively using + * the unit accessors. It also can be converted to a fiat amount by providing the + * corresponding BTC/fiat exchange rate. + * + * @example + * ```javascript + * var sats = Unit.fromBTC(1.3).toSatoshis(); + * var mili = Unit.fromBits(1.3).to(Unit.mBTC); + * var bits = Unit.fromFiat(1.3, 350).bits; + * var btc = new Unit(1.3, Unit.bits).BTC; + * ``` + * + * @param {Number} amount - The amount to be represented + * @param {String|Number} code - The unit of the amount or the exchange rate + * @returns {Unit} A new instance of an Unit + * @constructor + */ +function Unit(amount, code) { + if (!(this instanceof Unit)) { + return new Unit(amount, code); + } + + // convert fiat to BTC + if (_.isNumber(code)) { + if (code <= 0) { + throw new errors.Unit.InvalidRate(code); + } + amount = amount / code; + code = Unit.BTC; + } + + this._value = this._from(amount, code); + + var self = this; + var defineAccesor = function (key) { + Object.defineProperty(self, key, { + get: function () { return self.to(key); }, + enumerable: true, + }); + }; + + Object.keys(UNITS).forEach(defineAccesor); +} + +Object.keys(UNITS).forEach(function (key) { + Unit[key] = key; +}); + +/** + * Returns a Unit instance created from JSON string or object + * + * @param {String|Object} json - JSON with keys: amount and code + * @returns {Unit} A Unit instance + */ +Unit.fromObject = function fromObject(data) { + $.checkArgument(_.isObject(data), 'Argument is expected to be an object'); + return new Unit(data.amount, data.code); +}; + +/** + * Returns a Unit instance created from an amount in BTC + * + * @param {Number} amount - The amount in BTC + * @returns {Unit} A Unit instance + */ +Unit.fromBTC = function (amount) { + return new Unit(amount, Unit.BTC); +}; + +/** + * Returns a Unit instance created from an amount in mBTC + * + * @param {Number} amount - The amount in mBTC + * @returns {Unit} A Unit instance + */ +Unit.fromMillis = Unit.fromMilis = function (amount) { + return new Unit(amount, Unit.mBTC); +}; + +/** + * Returns a Unit instance created from an amount in bits + * + * @param {Number} amount - The amount in bits + * @returns {Unit} A Unit instance + */ +Unit.fromMicros = Unit.fromBits = function (amount) { + return new Unit(amount, Unit.bits); +}; + +/** + * Returns a Unit instance created from an amount in satoshis + * + * @param {Number} amount - The amount in satoshis + * @returns {Unit} A Unit instance + */ +Unit.fromSatoshis = function (amount) { + return new Unit(amount, Unit.satoshis); +}; + +/** + * Returns a Unit instance created from a fiat amount and exchange rate. + * + * @param {Number} amount - The amount in fiat + * @param {Number} rate - The exchange rate BTC/fiat + * @returns {Unit} A Unit instance + */ +Unit.fromFiat = function (amount, rate) { + return new Unit(amount, rate); +}; + +Unit.prototype._from = function (amount, code) { + if (!UNITS[code]) { + throw new errors.Unit.UnknownCode(code); + } + return parseInt((amount * UNITS[code][0]).toFixed()); +}; + +/** + * Returns the value represented in the specified unit + * + * @param {String|Number} code - The unit code or exchange rate + * @returns {Number} The converted value + */ +Unit.prototype.to = function (code) { + if (_.isNumber(code)) { + if (code <= 0) { + throw new errors.Unit.InvalidRate(code); + } + return parseFloat((this.BTC * code).toFixed(2)); + } + + if (!UNITS[code]) { + throw new errors.Unit.UnknownCode(code); + } + + var value = this._value / UNITS[code][0]; + return parseFloat(value.toFixed(UNITS[code][1])); +}; + +/** + * Returns the value represented in BTC + * + * @returns {Number} The value converted to BTC + */ +Unit.prototype.toBTC = function () { + return this.to(Unit.BTC); +}; + +/** + * Returns the value represented in mBTC + * + * @returns {Number} The value converted to mBTC + */ +Unit.prototype.toMillis = Unit.prototype.toMilis = function () { + return this.to(Unit.mBTC); +}; + +/** + * Returns the value represented in bits + * + * @returns {Number} The value converted to bits + */ +Unit.prototype.toMicros = Unit.prototype.toBits = function () { + return this.to(Unit.bits); +}; + +/** + * Returns the value represented in satoshis + * + * @returns {Number} The value converted to satoshis + */ +Unit.prototype.toSatoshis = function () { + return this.to(Unit.satoshis); +}; + +/** + * Returns the value represented in fiat + * + * @param {string} rate - The exchange rate between BTC/currency + * @returns {Number} The value converted to satoshis + */ +Unit.prototype.atRate = function (rate) { + return this.to(rate); +}; + +/** + * Returns a the string representation of the value in satoshis + * + * @returns {string} the value in satoshis + */ +Unit.prototype.toString = function () { + return this.satoshis + ' satoshis'; +}; + +/** + * Returns a plain object representation of the Unit + * + * @returns {Object} An object with the keys: amount and code + */ +Unit.prototype.toObject = Unit.prototype.toJSON = function toObject() { + return { + amount: this.BTC, + code: Unit.BTC + }; +}; + +/** + * Returns a string formatted for the console + * + * @returns {string} the value in satoshis + */ +Unit.prototype.inspect = function () { + return ''; +}; + +export default Unit; diff --git a/src/backend/wallet/lib/uri.js b/src/backend/wallet/lib/uri.js new file mode 100644 index 0000000..4f7c228 --- /dev/null +++ b/src/backend/wallet/lib/uri.js @@ -0,0 +1,223 @@ +'use strict'; + +import _ from 'lodash'; +import URL from 'url'; + +import Address from './address'; +import Unit from './unit'; + +/** + * Bitcore URI + * + * Instantiate an URI from a bitcoin URI String or an Object. An URI instance + * can be created with a bitcoin uri string or an object. All instances of + * URI are valid, the static method isValid allows checking before instantiation. + * + * All standard parameters can be found as members of the class, the address + * is represented using an {Address} instance and the amount is represented in + * satoshis. Any other non-standard parameters can be found under the extra member. + * + * @example + * ```javascript + * + * var uri = new URI('bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2'); + * console.log(uri.address, uri.amount); + * ``` + * + * @param {string|Object} data - A bitcoin URI string or an Object + * @param {Array.=} knownParams - Required non-standard params + * @throws {TypeError} Invalid bitcoin address + * @throws {TypeError} Invalid amount + * @throws {Error} Unknown required argument + * @returns {URI} A new valid and frozen instance of URI + * @constructor + */ +var URI = function (data, knownParams) { + if (!(this instanceof URI)) { + return new URI(data, knownParams); + } + + this.extras = {}; + this.knownParams = knownParams || []; + this.address = this.network = this.amount = this.message = null; + + if (typeof (data) === 'string') { + var params = URI.parse(data); + if (params.amount) { + params.amount = this._parseAmount(params.amount); + } + this._fromObject(params); + } else if (typeof (data) === 'object') { + this._fromObject(data); + } else { + throw new TypeError('Unrecognized data format.'); + } +}; + +/** + * Instantiate a URI from a String + * + * @param {string} str - JSON string or object of the URI + * @returns {URI} A new instance of a URI + */ +URI.fromString = function fromString(str) { + if (typeof (str) !== 'string') { + throw new TypeError('Expected a string'); + } + return new URI(str); +}; + +/** + * Instantiate a URI from an Object + * + * @param {Object} data - object of the URI + * @returns {URI} A new instance of a URI + */ +URI.fromObject = function fromObject(json) { + return new URI(json); +}; + +/** + * Check if an bitcoin URI string is valid + * + * @example + * ```javascript + * + * var valid = URI.isValid('bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu'); + * // true + * ``` + * + * @param {string|Object} data - A bitcoin URI string or an Object + * @param {Array.=} knownParams - Required non-standard params + * @returns {boolean} Result of uri validation + */ +URI.isValid = function (arg, knownParams) { + try { + new URI(arg, knownParams); + } catch (err) { + return false; + } + return true; +}; + +/** + * Convert a bitcoin URI string into a simple object. + * + * @param {string} uri - A bitcoin URI string + * @throws {TypeError} Invalid bitcoin URI + * @returns {Object} An object with the parsed params + */ +URI.parse = function (uri) { + var info = URL.parse(uri, true); + + if (info.protocol !== 'bitcoin:') { + throw new TypeError('Invalid bitcoin URI'); + } + + // workaround to host insensitiveness + var group = /[^:]*:\/?\/?([^?]*)/.exec(uri); + info.query.address = group && group[1] || undefined; + + return info.query; +}; + +URI.Members = ['address', 'amount', 'message', 'label', 'r']; + +/** + * Internal function to load the URI instance with an object. + * + * @param {Object} obj - Object with the information + * @throws {TypeError} Invalid bitcoin address + * @throws {TypeError} Invalid amount + * @throws {Error} Unknown required argument + */ +URI.prototype._fromObject = function (obj) { + /* jshint maxcomplexity: 10 */ + + if (!Address.isValid(obj.address)) { + throw new TypeError('Invalid bitcoin address'); + } + + this.address = new Address(obj.address); + this.network = this.address.network; + this.amount = obj.amount; + + for (var key in obj) { + if (key === 'address' || key === 'amount') { + continue; + } + + if (/^req-/.exec(key) && this.knownParams.indexOf(key) === -1) { + throw Error('Unknown required argument ' + key); + } + + var destination = URI.Members.indexOf(key) > -1 ? this : this.extras; + destination[key] = obj[key]; + } +}; + +/** + * Internal function to transform a BTC string amount into satoshis + * + * @param {string} amount - Amount BTC string + * @throws {TypeError} Invalid amount + * @returns {Object} Amount represented in satoshis + */ +URI.prototype._parseAmount = function (amount) { + amount = Number(amount); + if (isNaN(amount)) { + throw new TypeError('Invalid amount'); + } + return Unit.fromBTC(amount).toSatoshis(); +}; + +URI.prototype.toObject = URI.prototype.toJSON = function toObject() { + var json = {}; + for (var i = 0; i < URI.Members.length; i++) { + var m = URI.Members[i]; + if (this.hasOwnProperty(m) && typeof (this[m]) !== 'undefined') { + json[m] = this[m].toString(); + } + } + _.extend(json, this.extras); + return json; +}; + +/** + * Will return a the string representation of the URI + * + * @returns {string} Bitcoin URI string + */ +URI.prototype.toString = function () { + var query = {}; + if (this.amount) { + query.amount = Unit.fromSatoshis(this.amount).toBTC(); + } + if (this.message) { + query.message = this.message; + } + if (this.label) { + query.label = this.label; + } + if (this.r) { + query.r = this.r; + } + _.extend(query, this.extras); + + return URL.format({ + protocol: 'bitcoin:', + host: this.address, + query: query + }); +}; + +/** + * Will return a string formatted for the console + * + * @returns {string} Bitcoin URI + */ +URI.prototype.inspect = function () { + return ''; +}; + +export default URI; diff --git a/src/backend/wallet/lib/util/betitem.js b/src/backend/wallet/lib/util/betitem.js new file mode 100644 index 0000000..f129192 --- /dev/null +++ b/src/backend/wallet/lib/util/betitem.js @@ -0,0 +1,41 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; + +var BetItem = function BetItem(arg) { + if (!(this instanceof BetItem)) { + return new BetItem(arg); + } + var info = BetItem._from(arg); + this.playType = info.playType; + this.betType = info.betType; + this.times = info.times; + this.money = info.money; + + return this; +}; + +BetItem._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = BetItem._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for BetItem'); + } + return info; +}; + +BetItem._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + playType: data.playType, + betType: data.betType, + times: data.times, + money: data.money + }; + return info; +}; + +export default BetItem; \ No newline at end of file diff --git a/src/backend/wallet/lib/util/buffer.js b/src/backend/wallet/lib/util/buffer.js new file mode 100644 index 0000000..f9297aa --- /dev/null +++ b/src/backend/wallet/lib/util/buffer.js @@ -0,0 +1,185 @@ +'use strict'; + +import buffer from 'buffer'; +import assert from 'assert'; + +import js from './js'; +import $ from './preconditions'; + +function equals(a, b) { + if (a.length !== b.length) { + return false; + } + var length = a.length; + for (var i = 0; i < length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +const myBuffer = { + buffer: buffer, + Buffer: buffer.Buffer, + /** + * Fill a buffer with a value. + * + * @param {Buffer} buffer + * @param {number} value + * @return {Buffer} + */ + fill: function fill(buffer, value) { + $.checkArgumentType(buffer, 'Buffer', 'buffer'); + $.checkArgumentType(value, 'number', 'value'); + var length = buffer.length; + for (var i = 0; i < length; i++) { + buffer[i] = value; + } + return buffer; + }, + + /** + * Return a copy of a buffer + * + * @param {Buffer} original + * @return {Buffer} + */ + copy: function (original) { + var buffer = Buffer.alloc(original.length); + original.copy(buffer); + return buffer; + }, + + /** + * Returns true if the given argument is an instance of a buffer. Tests for + * both node's Buffer and Uint8Array + * + * @param {*} arg + * @return {boolean} + */ + isBuffer: function isBuffer(arg) { + + if (!buffer.Buffer) { + return + } + return buffer.Buffer.isBuffer(arg) || arg instanceof Uint8Array; + }, + + /** + * Returns a zero-filled byte array + * + * @param {number} bytes + * @return {Buffer} + */ + emptyBuffer: function emptyBuffer(bytes) { + $.checkArgumentType(bytes, 'number', 'bytes'); + var result = new buffer.Buffer(bytes); + for (var i = 0; i < bytes; i++) { + result.write('\0', i); + } + return result; + }, + + /** + * Concatenates a buffer + * + * Shortcut for buffer.Buffer.concat + */ + concat: buffer.Buffer.concat, + + equals: equals, + equal: equals, + + /** + * Transforms a number from 0 to 255 into a Buffer of size 1 with that value + * + * @param {number} integer + * @return {Buffer} + */ + integerAsSingleByteBuffer: function integerAsSingleByteBuffer(integer) { + $.checkArgumentType(integer, 'number', 'integer'); + return new buffer.Buffer([integer & 0xff]); + }, + + /** + * Transform a 4-byte integer into a Buffer of length 4. + * + * @param {number} integer + * @return {Buffer} + */ + integerAsBuffer: function integerAsBuffer(integer) { + $.checkArgumentType(integer, 'number', 'integer'); + var bytes = []; + bytes.push((integer >> 24) & 0xff); + bytes.push((integer >> 16) & 0xff); + bytes.push((integer >> 8) & 0xff); + bytes.push(integer & 0xff); + return Buffer.from(bytes); + }, + + /** + * Transform the first 4 values of a Buffer into a number, in little endian encoding + * + * @param {Buffer} buffer + * @return {number} + */ + integerFromBuffer: function integerFromBuffer(buffer) { + $.checkArgumentType(buffer, 'Buffer', 'buffer'); + return buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]; + }, + + /** + * Transforms the first byte of an array into a number ranging from -128 to 127 + * @param {Buffer} buffer + * @return {number} + */ + integerFromSingleByteBuffer: function integerFromBuffer(buffer) { + $.checkArgumentType(buffer, 'Buffer', 'buffer'); + return buffer[0]; + }, + + /** + * Transforms a buffer into a string with a number in hexa representation + * + * Shorthand for buffer.toString('hex') + * + * @param {Buffer} buffer + * @return {string} + */ + bufferToHex: function bufferToHex(buffer) { + $.checkArgumentType(buffer, 'Buffer', 'buffer'); + return buffer.toString('hex'); + }, + + /** + * Reverse a buffer + * @param {Buffer} param + * @return {Buffer} + */ + reverse: function reverse(param) { + var ret = new buffer.Buffer.alloc(param.length); + for (var i = 0; i < param.length; i++) { + ret[i] = param[param.length - i - 1]; + } + return ret; + }, + + /** + * Transforms an hexa encoded string into a Buffer with binary values + * + * Shorthand for Buffer(string, 'hex') + * + * @param {string} string + * @return {Buffer} + */ + hexToBuffer: function hexToBuffer(string) { + assert(js.isHexa(string)); + return new buffer.Buffer(string, 'hex'); + } +}; + +export default myBuffer + +export const NULL_HASH = myBuffer.fill(Buffer.alloc(32), 0); +export const EMPTY_BUFFER = Buffer.alloc(0); diff --git a/src/backend/wallet/lib/util/js.js b/src/backend/wallet/lib/util/js.js new file mode 100644 index 0000000..72a08a5 --- /dev/null +++ b/src/backend/wallet/lib/util/js.js @@ -0,0 +1,84 @@ +'use strict'; + +import _ from 'lodash'; + +/** + * Determines whether a string contains only hexadecimal values + * + * @name JSUtil.isHexa + * @param {string} value + * @return {boolean} true if the string is the hexa representation of a number + */ +var isHexa = function isHexa(value) { + if (!_.isString(value)) { + return false; + } + return /^[0-9a-fA-F]+$/.test(value); +}; + +/** + * @namespace JSUtil + */ +export default { + /** + * Test if an argument is a valid JSON object. If it is, returns a truthy + * value (the json object decoded), so no double JSON.parse call is necessary + * + * @param {string} arg + * @return {Object|boolean} false if the argument is not a JSON string. + */ + isValidJSON: function isValidJSON(arg) { + var parsed; + if (!_.isString(arg)) { + return false; + } + try { + parsed = JSON.parse(arg); + } catch (e) { + return false; + } + if (typeof (parsed) === 'object') { + return true; + } + return false; + }, + isHexa: isHexa, + isHexaString: isHexa, + + /** + * Clone an array + */ + cloneArray: function (array) { + return [].concat(array); + }, + + /** + * Define immutable properties on a target object + * + * @param {Object} target - An object to be extended + * @param {Object} values - An object of properties + * @return {Object} The target object + */ + defineImmutable: function defineImmutable(target, values) { + Object.keys(values).forEach(function (key) { + Object.defineProperty(target, key, { + configurable: false, + enumerable: true, + value: values[key] + }); + }); + return target; + }, + /** + * Checks that a value is a natural number, a positive integer or zero. + * + * @param {*} value + * @return {Boolean} + */ + isNaturalNumber: function isNaturalNumber(value) { + return typeof value === 'number' && + isFinite(value) && + Math.floor(value) === value && + value >= 0; + } +}; diff --git a/src/backend/wallet/lib/util/preconditions.js b/src/backend/wallet/lib/util/preconditions.js new file mode 100644 index 0000000..028c904 --- /dev/null +++ b/src/backend/wallet/lib/util/preconditions.js @@ -0,0 +1,39 @@ +'use strict'; + +import errors from '../errors'; +import _ from 'lodash'; +import _buffer from 'buffer'; + +export default { + checkState: function (condition, message) { + if (!condition) { + throw new errors.InvalidState(message); + } + }, + checkArgument: function (condition, argumentName, message, docsPath) { + if (!condition) { + throw new errors.InvalidArgument(argumentName, message, docsPath); + } + }, + checkArgumentType: function (argument, type, argumentName) { + argumentName = argumentName || '(unknown name)'; + if (_.isString(type)) { + if (type === 'Buffer') { + var buffer = _buffer; // './buffer' fails on cordova & RN + + if (!buffer.Buffer) { + return + } + if (!buffer.Buffer.isBuffer(argument)) { + throw new errors.InvalidArgumentType(argument, type, argumentName); + } + } else if (typeof argument !== type) { + throw new errors.InvalidArgumentType(argument, type, argumentName); + } + } else { + if (!(argument instanceof type)) { + throw new errors.InvalidArgumentType(argument, type.name, argumentName); + } + } + } +}; diff --git a/src/backend/wallet/lib/util/util.js b/src/backend/wallet/lib/util/util.js new file mode 100644 index 0000000..863fb4b --- /dev/null +++ b/src/backend/wallet/lib/util/util.js @@ -0,0 +1,246 @@ +'use strict'; + +import _ from 'lodash'; +import buffer from 'buffer'; +import assert from 'assert'; +import Decimal from 'decimal' + +import js from './js'; +import $ from './preconditions'; +import BN from '../crypto/bn' +import errors from '../errors'; +import BetItem from './betitem' +import VoteFund from './votefund' +import BufferWriter from '../encoding/bufferwriter'; + +export default { + + writeVarInt: function writeVarInt(n, value) { + $.checkArgumentType(n, 'number', 'n') + $.checkArgumentType(value, 'number', 'value'); + + var bufLen = parseInt((n * 8 + 6) / 7) + var len = 0 + + var tmpbuf = Buffer.alloc(bufLen) + + while (true) { + tmpbuf[len] = (value & 0x7F) | (len ? 0x80 : 0x00) + if (value <= 0x7F) + break + value = parseInt(value / 128) - 1; + len++ + } + + var buf = Buffer.alloc(len + 1) + var i = 0; + do { + + buf[i] = tmpbuf[len] + i++ + + } while (len--) + + return buf + }, + + checkNumber: function checkNumber(theObj) { + var reg = /^[0-9]*$/; + if (reg.test(theObj)) { + return true; + } + return false; + }, + + isSimpleRegIdStr: function isSimpleRegIdStr(srcRegID) { + var len = srcRegID.length + if (len < 3) + return false + var arr = srcRegID.split('-') + var size = arr.length + + if (size != 2) + return false + + var strHeight = arr[0] + if (strHeight.length > 10 || strHeight.length == 0) + return false + + if (!this.checkNumber(strHeight)) + return false + + var strIndex = arr[1] + if (strIndex.length > 10 || strIndex.length == 0) + return false + + if (!this.checkNumber(strIndex)) + return false + + var REGID = { + height: parseInt(strHeight), + index: parseInt(strIndex) + } + + return REGID + + }, + + splitRegID: function splitRegID(srcRegID) { + return this.isSimpleRegIdStr(srcRegID) + }, + + getSpcContractData: function getSpcContractData(address, spc) { + $.checkArgumentType(address, 'string', 'address'); + $.checkArgumentType(spc, 'number', 'spc'); + + var writer = new BufferWriter(); + writer.writeUInt8(0xf0) + writer.writeUInt8(0x07) + + var addrBuf = new buffer.Buffer(address, 'utf8'); + writer.write(addrBuf) + + var bnSpc = BN.fromNumber(spc) + + writer.writeUInt64LEBN(bnSpc) + + return writer.toBuffer() + + }, + + getBetContractData: function getBetContractData(lid, address, ltype, betList) { + $.checkArgumentType(lid, 'string', 'lid'); + $.checkArgumentType(address, 'string', 'address'); + $.checkArgumentType(ltype, 'number', 'ltype'); + $.checkArgumentType(betList, 'object', 'betList'); + + if (!betList instanceof Array) { + throw new errors.InvalidArgumentType(betList, 'array', 'betList'); + } + + var writer = new BufferWriter(); + writer.writeUInt8(0xf0) + writer.writeUInt8(0x02) + + var lidBuf = new buffer.Buffer(lid, 'utf8') + writer.write(lidBuf) + + var addrBuf = new buffer.Buffer(address, 'utf8') + writer.write(addrBuf) + + writer.writeUInt8(ltype) + + var len = betList.length + writer.writeUInt32LE(len) + for (var i = 0; i < len; i++) { + var betItem = betList[i] + + if (!betItem instanceof BetItem) { + throw new errors.InvalidArgumentType(betItem, 'BetItem', 'betItem'); + } + + writer.writeUInt8(betItem.playType) + writer.writeUInt8(betItem.betType) + writer.writeUInt32LE(betItem.times) + + var bnMoney = BN.fromNumber(betItem.money) + + writer.writeUInt64LEBN(bnMoney) + } + + return writer.toBuffer() + }, + + getDelegateData: function getDelegateData(delegateList) { + $.checkArgumentType(delegateList, 'object', 'delegateList'); + + if (!delegateList instanceof Array) { + throw new errors.InvalidArgumentType(delegateList, 'array', 'delegateList'); + } + + var writer = new BufferWriter(); + var len = delegateList.length + writer.writeVarintNum(len) + for (var i = 0; i < len; i++) { + var votefund = delegateList[i] + if (!votefund instanceof VoteFund) { + throw new errors.InvalidArgumentType(votefund, 'VoteFund', 'votefund'); + } + + writer.writeUInt8(votefund.operType) + + var pubkeyBuf = new Buffer(votefund.pubkey, 'hex') + var publen = pubkeyBuf.length + writer.writeVarintNum(publen) + writer.write(pubkeyBuf); + + var valueBuf = this.writeVarInt(8, votefund.value) + writer.write(valueBuf) + } + + return writer.toBuffer(); + }, + + safeAdd: function safeAdd(a, b) { + return new Decimal(a).add(new Decimal(b)).toNumber() + }, + + safeSub: function safeSub(a, b) { + return new Decimal(a).sub(new Decimal(b)).toNumber() + }, + + safeMul: function safeMul(a, b) { + return new Decimal(a).mul(new Decimal(b)).toNumber() + }, + + safeDiv: function safeDiv(a, b) { + return new Decimal(a).div(new Decimal(b)).toNumber() + }, + + // check format for the regId + checkRegId: function checkRegId(regId) { + var regIdData = this.splitRegID(regId) + if (_.isNull(regIdData.height) || _.isUndefined(regIdData.height)) + return false + return true + }, + + + isSpaceChar: function isSpaceChar(char) { + return char.match(/\s/) + }, + + isHexChar: function isHexChar(char) { + return char.match(/[0-9A-Fa-f]/) + }, + + hexToBytes: function hexToBytes(hexStr) { + assert(_.isString(hexStr)) + + var bytes = []; + var curChar = ""; + var leftChar = "" + + for (var pos = 0; pos < hexStr.length; pos++) { + curChar = hexStr.charAt(pos); + if (this.isSpaceChar(curChar)) { + continue; + } + if (!this.isHexChar(hexStr)) { + // invalid hex char + return null; + } + if (leftChar == "") { + leftChar = curChar; + } + else { + bytes.push(parseInt(leftChar + curChar, 16)); + leftChar = ""; + } + } + if (leftChar != "") { + return null; + } + return bytes; + } +}; \ No newline at end of file diff --git a/src/backend/wallet/lib/util/votefund.js b/src/backend/wallet/lib/util/votefund.js new file mode 100644 index 0000000..57fe167 --- /dev/null +++ b/src/backend/wallet/lib/util/votefund.js @@ -0,0 +1,43 @@ +'use strict'; + +import _ from 'lodash'; +import $ from '../util/preconditions'; + +var VoteFund = function VoteFund(arg) { + if (!(this instanceof VoteFund)) { + return new VoteFund(arg); + } + var info = VoteFund._from(arg); + this.operType = info.operType; + this.pubkey = info.pubkey; + this.value = info.value; + + return this; +}; + +VoteFund._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = VoteFund._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for VoteFund'); + } + return info; +}; + +VoteFund._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + operType: data.operType, + pubkey: data.pubkey, + value: data.value + }; + return info; +}; + +VoteFund.NULL_OPER = 0; +VoteFund.ADD_FUND = 1; +VoteFund.MINUS_FUND = 2; + +export default VoteFund; \ No newline at end of file diff --git a/src/backend/wallet/lib/util/writerhelper.js b/src/backend/wallet/lib/util/writerhelper.js new file mode 100644 index 0000000..e9c29eb --- /dev/null +++ b/src/backend/wallet/lib/util/writerhelper.js @@ -0,0 +1,244 @@ +'use strict'; + +import _ from 'lodash'; +import inherits from 'inherits'; +import BufferWriter from '../encoding/bufferwriter'; +import Util from './util.js' +import bufferUtil from './buffer'; +import assert from 'assert'; +import Address from '../address' + +/** + * @desc + * Wrapper around BufferWriter for enhanced extention + * + * @param {Object|string|WriterHelper} obj + * @constructor + */ +function WriterHelper(obj) { + if (!(this instanceof WriterHelper)) { + return new WriterHelper(obj); + } + if (obj instanceof WriterHelper) { + return obj; + } + BufferWriter.call(this) + +} + +inherits(WriterHelper, BufferWriter); + +WriterHelper.prototype.writeVarInt = function (sz, value) { + var buf = Util.writeVarInt(sz, value) + this.write(buf) +}; + +// write the string len and string +WriterHelper.prototype.writeString = function (value) { + assert(value == undefined || _.isString(value) || bufferUtil.isBuffer(value)) + var len = 0 + var buf = value + if (!_.isEmpty(value)) { + if (!bufferUtil.isBuffer(buf)) { + buf = Buffer.from(buf) + } + len = buf.length + } + this.writeVarintNum(len) + if (len > 0) { + this.write(buf) + } +}; + +//write the cdp asset +WriterHelper.prototype.writeCdpAsset = function (map) { + if (map != null || map.size != 0) { + this.writeVarintNum(map.size) + for (let item of map.entries()) { + this.writeString(item[0]) + this.writeVarInt(8, item[1]) + } + } +} + +// write the buf len and buf +WriterHelper.prototype.writeBuf = function (value) { + var len = 0 + if (!_.isEmpty(value)) { + assert(bufferUtil.isBuffer(value)) + len = value.length + } + this.writeVarintNum(len) + if (len > 0) { + this.write(value) + } +}; + +WriterHelper.prototype.writeRegId = function (value) { + var regIdData = Util.splitRegID(value) + + var intWriter = new WriterHelper() + intWriter.writeVarInt(4, regIdData.height) + intWriter.writeVarInt(2, regIdData.index) + this.writeBuf(intWriter.toBuffer()) +}; + +WriterHelper.prototype.VoteOperType = { + ADD_FUND: 1, //!< add operate + MINUS_FUND: 2 //!< minus operate +} + +/** + * delegateData array, item is object data of delegate + * [ + * { + * publicKey: string the public key that the votes are received, + * votes: number vote number + * }, + *] + */ +WriterHelper.prototype.writeDelegateData = function (delegateData) { + + this.writeVarintNum(delegateData.length); + + for (var i = 0; i < delegateData.length; i++) { + var operType = this.VoteOperType.ADD_FUND; + if (delegateData[0].votes < 0) { + operType = this.VoteOperType.MINUS_FUND; + } + var votes_abs = Math.abs(delegateData[i].votes); + this.writeUInt8(operType); + if (delegateData[i].publicKey.indexOf("-") > -1) { + this.writeRegId(delegateData[i].publicKey) + } else { + this.writeString(Buffer.from(delegateData[i].publicKey, 'hex')); + } + this.writeVarInt(8, votes_abs) + } +} + +WriterHelper.prototype.writeDestArrData = function (destArr, network) { + + this.writeVarintNum(destArr.length); + + for (var i = 0; i < destArr.length; i++) { + var addr = Address.fromString(destArr[i].destAddr, network, 'pubkeyhash') + var size = addr.hashBuffer.length + this.writeUInt8(size) + this.write(addr.hashBuffer) + this.writeString(destArr[i].coinType) + this.writeVarInt(8, destArr[i].value) + } +} + +WriterHelper.prototype.writeAssetData = function (assetData, network) { + this.writeString(assetData.assetSymbol) + this.writeRegId(assetData.ownerRegid) + this.writeString(assetData.assetName) + var minTable = 1 + if (!assetData.modifiAble) minTable = 0 + this.writeUInt8(minTable) + this.writeVarInt(8, assetData.totalSupply) +} + +WriterHelper.prototype.UpdateAssetType = { + UPDATE_NONE: 0, + OWNER_UID: 1, + NAME: 2, + MINT_AMOUNT: 3, +} + +WriterHelper.prototype.writeassetUpdateData = function (assetUpdateData, network) { + var type = assetUpdateData.updateType + switch (type) { + case 1: + this.writeUInt8(1) + this.writeRegId(assetUpdateData.updateValue) + break; + case 2: + this.writeUInt8(2) + this.writeString(assetUpdateData.updateValue) + break; + case 3: + this.writeUInt8(3) + this.writeVarInt(8, assetUpdateData.updateValue) + break; + } +} + +WriterHelper.prototype.writeFeedPriceData = function (feedPriceData) { + + this.writeVarintNum(feedPriceData.length); + + for (var i = 0; i < feedPriceData.length; i++) { + var coinPriceType = feedPriceData[i].coinPriceType; + var coinType = coinPriceType.coinType; + var priceType = coinPriceType.priceType; + + var price = feedPriceData[i].price; + this.writeVarintNum(coinType); + this.writeVarintNum(priceType); + this.writeVarInt(8, price) + } +} + +WriterHelper.prototype.writeDexSettleData = function (dexsettledata) { + + this.writeVarintNum(dexsettledata.length); + + for (var i = 0; i < dexsettledata.length; i++) { + var buyOrderId = dexsettledata[i].buyOrderId; + var sellOrderId = dexsettledata[i].sellOrderId; + var dealPrice = dexsettledata[i].dealPrice; + var dealCoinAmount = dexsettledata[i].dealCoinAmount; + var dealAssetAmount = dexsettledata[i].dealAssetAmount; + + var buyOrderIdBuf = new Buffer.from(buyOrderId, 'hex'); + this.writeVarintNum(buyOrderIdBuf.length); + this.write(bufferUtil.reverse(buyOrderIdBuf)); + + var sellOrderIdBuf = new Buffer.from(sellOrderId, 'hex'); + this.writeVarintNum(sellOrderIdBuf.length); + this.write(bufferUtil.reverse(sellOrderIdBuf)); + + this.writeVarInt(8, dealPrice); + this.writeVarInt(8, dealCoinAmount); + this.writeVarInt(8, dealAssetAmount); + } +} + +WriterHelper.prototype.PriceType = { + USD: 0, + CNY: 1, + EUR: 2, + BTC: 10, + USDT: 11, + GOLD: 20, + KWH: 100 +} + +WriterHelper.prototype.CoinType = { + WICC: "WICC", + WGRT: "WGRT", + WUSD: "WUSD", + WCNY: "WCNY", + + WBTC: "WBTC", + WETH: "WETH", + WEOS: "WEOS", + + USD: "USD", + CNY: "CNY", + EUR: "EUR", + BTC: "BTC", + USDT: "USDT", + GOLD: "GOLD", + KWH: "KWH", +} +WriterHelper.prototype.BalanceOpType = { + NULL_OP: 0, + ADD_FREE: 1, + SUB_FREE: 2 +} + +export default WriterHelper; diff --git a/src/backend/wallet/lib/wiccapi.js b/src/backend/wallet/lib/wiccapi.js new file mode 100644 index 0000000..f1977ec --- /dev/null +++ b/src/backend/wallet/lib/wiccapi.js @@ -0,0 +1,442 @@ +'use strict'; + +import _ from 'lodash'; +import $ from './util/preconditions'; +import Mnemonic from './mnemonic' +import Address from './address' +import Random from './crypto/random' +import Hash from './crypto/hash' +import buffer from 'buffer' +import scrypt from 'scryptsy' +import aes from './aes-cbc' +import CryptoJS from 'crypto-js' +import HDPrivateKey from './hdprivatekey' +import CustomBuffer from './util/buffer' +import registeraccounttx from './lib/transaction/registeraccounttx' +import commontx from './lib/transaction/commontx' +import contracttx from './lib/transaction/contracttx' +import registerapptx from './lib/transaction/registerapptx' +import delegatetx from './lib/transaction/delegatetx' +import assetcreatetx from './lib/transaction/assetcreatetx' +import assetupdatetx from './lib/transaction/assetupdatetx' +import ucointransfertx from './lib/transaction/ucointransfertx' +import ucontractinvoketx from './lib/transaction/ucontractinvoketx' +import cdpstaketx from './lib/transaction/cdpstaketx' +import cdpredeemtx from './lib/transaction/cdpredeemtx' +import cdpliquidatetx from './lib/transaction/cdpliquidatetx' +import dexbuylimitordertx from './lib/transaction/dexbuylimitordertx' +import dexselllimitordertx from './lib/transaction/dexselllimitordertx' +import dexbuymarketordertx from './lib/transaction/dexbuymarketordertx' +import dexsellmarketordertx from './lib/transaction/dexsellmarketordertx' +import dexcancelordertx from './lib/transaction/dexcancelordertx' + +var txMap = { + 2: { + txName: 'ACCOUNT_REGISTER_TX', + txAction: registeraccounttx + }, + 3: { + txName: 'BCOIN_TRANSFER_TX', + txAction: commontx + }, + 4: { + txName: 'LCONTRACT_INVOKE_TX', + txAction: contracttx + }, + 5: { + txName: 'LCONTRACT_DEPLOY_TX', + txAction: registerapptx + }, + 6: { + txName: 'DELEGATE_VOTE_TX', + txAction: delegatetx + }, + 9: { + txName: 'ASSET_ISSUE_TX', + txAction: assetcreatetx + }, + 10: { + txName: 'ASSET_UPDATE_TX', + txAction: assetupdatetx + }, + + 11: { + txName: 'UCOIN_TRANSFER_TX', + txAction: ucointransfertx //ucointransfertx + }, + 15: { + txName: 'UCOIN_CONTRACT_INVOKE_TX', + txAction: ucontractinvoketx + }, + 21: { + txName: 'CDP_STAKE_TX', + txAction: cdpstaketx + }, + 22: { + txName: 'CDP_REDEEMP_TX', + txAction: cdpredeemtx + }, + 23: { + txName: 'CDP_LIQUIDATE_TX', + txAction: cdpliquidatetx + }, + 84: { + txName: 'DEX_LIMIT_BUY_ORDER_TX', + txAction: dexbuylimitordertx + }, + 85: { + txName: ' DEX_LIMIT_SELL_ORDER_TX', + txAction: dexselllimitordertx + }, + 86: { + txName: 'DEX_MARKET_BUY_ORDER_TX', + txAction: dexbuymarketordertx + }, + 87: { + txName: 'DEX_MARKET_SELL_ORDER_TX', + txAction: dexsellmarketordertx + }, + 88: { + txName: 'DEX_CANCEL_ORDER_TX', + txAction: dexcancelordertx + } +} + +var WiccApi = function WiccApi(arg) { + if (!(this instanceof WiccApi)) { + return new WiccApi(arg); + } + var info = WiccApi._from(arg); + this.network = arg.network + return this; +}; + +WiccApi._from = function _from(arg) { + var info = {}; + if (_.isObject(arg)) { + info = WiccApi._fromObject(arg); + } else { + throw new TypeError('Unrecognized argument for WiccApi'); + } + return info; +}; + +WiccApi._fromObject = function _fromObject(data) { + $.checkArgument(data, 'data is required'); + + var info = { + network: data.network + }; + return info; +}; + +WiccApi.prototype.getBIP44Path = function () { + return this.network === "testnet" ? "m/44'/99999'/0'/0/0" : "m/44'/99999'/0'/0/0" +} + +WiccApi.prototype.createAllCoinMnemonicCode = function (language) { + language = language || "ENGLISH" + var code = new Mnemonic(Mnemonic.Words[language]) + var strCode = code.toString() + + return strCode +} + +WiccApi.prototype.getMnemonicCodeLanguage = function (mnemonic) { + let lang + var dicts = Object.keys(Mnemonic.Words); + for (var i = 0; i < dicts.length; i++) { + var key = dicts[i]; + if (Mnemonic._belongsToWordlist(mnemonic, Mnemonic.Words[key])) { + lang = key + break + } + } + return lang +} + +WiccApi.prototype.switchMnemonicCode = function (mnemonic, targetLanguage) { + if (!targetLanguage) return mnemonic + var lang = "" + var ret = [] + var indexArr = [] + var dicts = Object.keys(Mnemonic.Words); + if (dicts.indexOf(targetLanguage) === -1) { + throw `${targetLanguage} is not supported` + } + for (var i = 0; i < dicts.length; i++) { + var key = dicts[i]; + if (Mnemonic._belongsToWordlist(mnemonic, Mnemonic.Words[key])) { + lang = key + break + } + } + if (lang === targetLanguage) return mnemonic; + var wordArr = mnemonic.split(" ") + indexArr = wordArr.map(item => { + return Mnemonic.Words[lang].indexOf(item) + }) + indexArr.map(item => { + ret.push(Mnemonic.Words[targetLanguage][item]) + }) + return ret.join(" ") +} + +WiccApi.prototype.checkMnemonicCode = function (mnemonic) { + return Mnemonic.isValid(mnemonic) +} + +WiccApi.prototype.validateAddress = function (address) { + return Address.isValid(address, this.network) +} + +WiccApi.prototype.getPriKeyFromMnemonicCode = function (mnemonic) { + mnemonic = this.switchMnemonicCode(mnemonic, "ENGLISH") + var code = new Mnemonic(mnemonic) + var xpriv = code.toHDPrivateKey(null, this.network); + var p = xpriv.deriveChild(this.getBIP44Path()); + return p.privateKey.toWIF() +} + +WiccApi.prototype.getAddressFromMnemonicCode = function (mnemonic) { + mnemonic = this.switchMnemonicCode(mnemonic, "ENGLISH") + var code = new Mnemonic(mnemonic) + var xpriv = code.toHDPrivateKey(null, this.network); + var p = xpriv.deriveChild(this.getBIP44Path()); + return p.privateKey.toAddress() +} + +WiccApi.prototype.createWallet = function (mnemonic, password) { + mnemonic = this.switchMnemonicCode(mnemonic, "ENGLISH") + var salt = Random.getRandomBuffer(8) + + var passbuf = new buffer.Buffer(password, 'utf8'); + var hashpwd = Hash.sha256(passbuf) + + var code = new Mnemonic(mnemonic) + var strCode = code.toString() + + var seed = code.toSeed() + + var xpriv = code.toHDPrivateKey(null, this.network); + var p = xpriv.deriveChild(this.getBIP44Path()); + var address = p.privateKey.toAddress() + var strAddress = address.toString() + + var d = new Date() + var creationTimeSeconds = parseInt(d.getTime() / 1000) + + + var data = scrypt(password, salt, 32768, 8, 1, 64) + + var key = data.slice(0, 32) + var iv = data.slice(32, 48) + + var hexKey = key.toString('hex') + var cryKey = CryptoJS.enc.Hex.parse(hexKey) + + var hexIv = iv.toString('hex') + var cryIv = CryptoJS.enc.Hex.parse(hexIv) + + var strSeed = seed.toString('hex') + var encryptedseed = aes.encrypt(cryKey, cryIv, strSeed) + var encryptedMne = aes.encrypt(cryKey, cryIv, strCode) + + var encSeedData = { + encryptedBytes: encryptedseed, + iv: iv + } + + var encMneData = { + encryptedBytes: encryptedMne, + iv: iv + } + + var seedinfo = { + encMneData: encMneData, + encSeedData: encSeedData, + creationTimeSeconds: creationTimeSeconds, + hashPwd: hashpwd, + salt: salt + } + + var wallinfo = { + seedinfo: seedinfo, + symbol: 'WICC', + address: strAddress + } + + return wallinfo + +} + +WiccApi.prototype.getHDPriKeyFromSeed = function (seedinfo, password, addressIndex) { + + var passbuf = new buffer.Buffer(password, 'utf8'); + var hashpwd = Hash.sha256(passbuf) + if (!CustomBuffer.equal(hashpwd, seedinfo.hashPwd)) { + return null + } + + var salt = seedinfo.salt + var data = scrypt(password, salt, 32768, 8, 1, 64) + + var key = data.slice(0, 32) + var iv = data.slice(32, 48) + + var hexKey = key.toString('hex') + var cryKey = CryptoJS.enc.Hex.parse(hexKey) + + var hexIv = iv.toString('hex') + var cryIv = CryptoJS.enc.Hex.parse(hexIv) + + var base64seed = seedinfo.encSeedData.encryptedBytes + var strseed = aes.decrypt(cryKey, cryIv, base64seed) + var seed = new Buffer(strseed, 'hex') + + var xpriv = HDPrivateKey.fromSeed(seed, this.network); + var p = xpriv.deriveChild("m/44'/99999'/0'/0/" + addressIndex); + + return p.privateKey.toWIF() +} + +WiccApi.prototype.getPriKeyFromSeed = function (seedinfo, password) { + + var passbuf = new buffer.Buffer(password, 'utf8'); + var hashpwd = Hash.sha256(passbuf) + if (!CustomBuffer.equal(hashpwd, seedinfo.hashPwd)) { + return null + } + + var salt = seedinfo.salt + var data = scrypt(password, salt, 32768, 8, 1, 64) + + var key = data.slice(0, 32) + var iv = data.slice(32, 48) + + var hexKey = key.toString('hex') + var cryKey = CryptoJS.enc.Hex.parse(hexKey) + + var hexIv = iv.toString('hex') + var cryIv = CryptoJS.enc.Hex.parse(hexIv) + + var base64seed = seedinfo.encSeedData.encryptedBytes + var strseed = aes.decrypt(cryKey, cryIv, base64seed) + var seed = new Buffer(strseed, 'hex') + /* + var code = Mnemonic.fromSeed(seed, Mnemonic.Words.ENGLISH) + var strCode = code.toString() + + var xpriv = code.toHDPrivateKey(null, 'testnet'); + */ + + var xpriv = HDPrivateKey.fromSeed(seed, this.network); + var p = xpriv.deriveChild(this.getBIP44Path()); + + return p.privateKey.toWIF() +} + +WiccApi.prototype.getMnemonicCodeFromSeed = function (seedinfo, password) { + + var passbuf = new buffer.Buffer(password, 'utf8'); + var hashpwd = Hash.sha256(passbuf) + if (!CustomBuffer.equal(hashpwd, seedinfo.hashPwd)) { + return null + } + + var salt = seedinfo.salt + var data = scrypt(password, salt, 32768, 8, 1, 64) + + var key = data.slice(0, 32) + var iv = data.slice(32, 48) + + var hexKey = key.toString('hex') + var cryKey = CryptoJS.enc.Hex.parse(hexKey) + + var hexIv = iv.toString('hex') + var cryIv = CryptoJS.enc.Hex.parse(hexIv) + + var base64Mne = seedinfo.encMneData.encryptedBytes + var Mne = aes.decrypt(cryKey, cryIv, base64Mne) + + return Mne +} + +WiccApi.prototype.changePassword = function (seedinfo, oldpassword, newpassword) { + + var passbuf = new buffer.Buffer(oldpassword, 'utf8'); + var hashpwd = Hash.sha256(passbuf) + if (!CustomBuffer.equal(hashpwd, seedinfo.hashPwd)) { + return null + } + + var salt = seedinfo.salt + var data = scrypt(oldpassword, salt, 32768, 8, 1, 64) + + var key = data.slice(0, 32) + var iv = data.slice(32, 48) + + var hexKey = key.toString('hex') + var cryKey = CryptoJS.enc.Hex.parse(hexKey) + + var hexIv = iv.toString('hex') + var cryIv = CryptoJS.enc.Hex.parse(hexIv) + + var base64seed = seedinfo.encSeedData.encryptedBytes + var strseed = aes.decrypt(cryKey, cryIv, base64seed) + var base64Mne = seedinfo.encMneData.encryptedBytes + var Mne = aes.decrypt(cryKey, cryIv, base64Mne) + + var newpassbuf = new buffer.Buffer(newpassword, 'utf8'); + var newhashpwd = Hash.sha256(newpassbuf) + + data = scrypt(newpassword, salt, 32768, 8, 1, 64) + + key = data.slice(0, 32) + iv = data.slice(32, 48) + + hexKey = key.toString('hex') + cryKey = CryptoJS.enc.Hex.parse(hexKey) + + hexIv = iv.toString('hex') + cryIv = CryptoJS.enc.Hex.parse(hexIv) + + var encryptedseed = aes.encrypt(cryKey, cryIv, strseed) + var encryptedMne = aes.encrypt(cryKey, cryIv, Mne) + + var encSeedData = { + encryptedBytes: encryptedseed, + iv: iv + } + + var encMneData = { + encryptedBytes: encryptedMne, + iv: iv + } + + var newseedinfo = { + encMneData: encMneData, + encSeedData: encSeedData, + creationTimeSeconds: seedinfo.creationTimeSeconds, + hashPwd: newhashpwd, + salt: salt + } + + return newseedinfo +} + +WiccApi.prototype.createSignTransaction = function (privkey, txData) { + var txConstructor = txMap[txData.nTxType].txAction + var txBody = new txConstructor(txData) + return txBody.SerializeTx(privkey) +} + +WiccApi.PROTOCAL_VERSION = 1; + +//设置交易类型 +// set transaction type +for (var item in txMap) { + WiccApi[txMap[item].txName] = Number(item) +} + +export default WiccApi; diff --git a/src/backend/wallet/lib/words/chinese.js b/src/backend/wallet/lib/words/chinese.js new file mode 100644 index 0000000..18646c0 --- /dev/null +++ b/src/backend/wallet/lib/words/chinese.js @@ -0,0 +1,5 @@ +'use strict'; + +var chinese = ['的', '一', '是', '在', '不', '了', '有', '和', '人', '这', '中', '大', '为', '上', '个', '国', '我', '以', '要', '他', '时', '来', '用', '们', '生', '到', '作', '地', '于', '出', '就', '分', '对', '成', '会', '可', '主', '发', '年', '动', '同', '工', '也', '能', '下', '过', '子', '说', '产', '种', '面', '而', '方', '后', '多', '定', '行', '学', '法', '所', '民', '得', '经', '十', '三', '之', '进', '着', '等', '部', '度', '家', '电', '力', '里', '如', '水', '化', '高', '自', '二', '理', '起', '小', '物', '现', '实', '加', '量', '都', '两', '体', '制', '机', '当', '使', '点', '从', '业', '本', '去', '把', '性', '好', '应', '开', '它', '合', '还', '因', '由', '其', '些', '然', '前', '外', '天', '政', '四', '日', '那', '社', '义', '事', '平', '形', '相', '全', '表', '间', '样', '与', '关', '各', '重', '新', '线', '内', '数', '正', '心', '反', '你', '明', '看', '原', '又', '么', '利', '比', '或', '但', '质', '气', '第', '向', '道', '命', '此', '变', '条', '只', '没', '结', '解', '问', '意', '建', '月', '公', '无', '系', '军', '很', '情', '者', '最', '立', '代', '想', '已', '通', '并', '提', '直', '题', '党', '程', '展', '五', '果', '料', '象', '员', '革', '位', '入', '常', '文', '总', '次', '品', '式', '活', '设', '及', '管', '特', '件', '长', '求', '老', '头', '基', '资', '边', '流', '路', '级', '少', '图', '山', '统', '接', '知', '较', '将', '组', '见', '计', '别', '她', '手', '角', '期', '根', '论', '运', '农', '指', '几', '九', '区', '强', '放', '决', '西', '被', '干', '做', '必', '战', '先', '回', '则', '任', '取', '据', '处', '队', '南', '给', '色', '光', '门', '即', '保', '治', '北', '造', '百', '规', '热', '领', '七', '海', '口', '东', '导', '器', '压', '志', '世', '金', '增', '争', '济', '阶', '油', '思', '术', '极', '交', '受', '联', '什', '认', '六', '共', '权', '收', '证', '改', '清', '美', '再', '采', '转', '更', '单', '风', '切', '打', '白', '教', '速', '花', '带', '安', '场', '身', '车', '例', '真', '务', '具', '万', '每', '目', '至', '达', '走', '积', '示', '议', '声', '报', '斗', '完', '类', '八', '离', '华', '名', '确', '才', '科', '张', '信', '马', '节', '话', '米', '整', '空', '元', '况', '今', '集', '温', '传', '土', '许', '步', '群', '广', '石', '记', '需', '段', '研', '界', '拉', '林', '律', '叫', '且', '究', '观', '越', '织', '装', '影', '算', '低', '持', '音', '众', '书', '布', '复', '容', '儿', '须', '际', '商', '非', '验', '连', '断', '深', '难', '近', '矿', '千', '周', '委', '素', '技', '备', '半', '办', '青', '省', '列', '习', '响', '约', '支', '般', '史', '感', '劳', '便', '团', '往', '酸', '历', '市', '克', '何', '除', '消', '构', '府', '称', '太', '准', '精', '值', '号', '率', '族', '维', '划', '选', '标', '写', '存', '候', '毛', '亲', '快', '效', '斯', '院', '查', '江', '型', '眼', '王', '按', '格', '养', '易', '置', '派', '层', '片', '始', '却', '专', '状', '育', '厂', '京', '识', '适', '属', '圆', '包', '火', '住', '调', '满', '县', '局', '照', '参', '红', '细', '引', '听', '该', '铁', '价', '严', '首', '底', '液', '官', '德', '随', '病', '苏', '失', '尔', '死', '讲', '配', '女', '黄', '推', '显', '谈', '罪', '神', '艺', '呢', '席', '含', '企', '望', '密', '批', '营', '项', '防', '举', '球', '英', '氧', '势', '告', '李', '台', '落', '木', '帮', '轮', '破', '亚', '师', '围', '注', '远', '字', '材', '排', '供', '河', '态', '封', '另', '施', '减', '树', '溶', '怎', '止', '案', '言', '士', '均', '武', '固', '叶', '鱼', '波', '视', '仅', '费', '紧', '爱', '左', '章', '早', '朝', '害', '续', '轻', '服', '试', '食', '充', '兵', '源', '判', '护', '司', '足', '某', '练', '差', '致', '板', '田', '降', '黑', '犯', '负', '击', '范', '继', '兴', '似', '余', '坚', '曲', '输', '修', '故', '城', '夫', '够', '送', '笔', '船', '占', '右', '财', '吃', '富', '春', '职', '觉', '汉', '画', '功', '巴', '跟', '虽', '杂', '飞', '检', '吸', '助', '升', '阳', '互', '初', '创', '抗', '考', '投', '坏', '策', '古', '径', '换', '未', '跑', '留', '钢', '曾', '端', '责', '站', '简', '述', '钱', '副', '尽', '帝', '射', '草', '冲', '承', '独', '令', '限', '阿', '宣', '环', '双', '请', '超', '微', '让', '控', '州', '良', '轴', '找', '否', '纪', '益', '依', '优', '顶', '础', '载', '倒', '房', '突', '坐', '粉', '敌', '略', '客', '袁', '冷', '胜', '绝', '析', '块', '剂', '测', '丝', '协', '诉', '念', '陈', '仍', '罗', '盐', '友', '洋', '错', '苦', '夜', '刑', '移', '频', '逐', '靠', '混', '母', '短', '皮', '终', '聚', '汽', '村', '云', '哪', '既', '距', '卫', '停', '烈', '央', '察', '烧', '迅', '境', '若', '印', '洲', '刻', '括', '激', '孔', '搞', '甚', '室', '待', '核', '校', '散', '侵', '吧', '甲', '游', '久', '菜', '味', '旧', '模', '湖', '货', '损', '预', '阻', '毫', '普', '稳', '乙', '妈', '植', '息', '扩', '银', '语', '挥', '酒', '守', '拿', '序', '纸', '医', '缺', '雨', '吗', '针', '刘', '啊', '急', '唱', '误', '训', '愿', '审', '附', '获', '茶', '鲜', '粮', '斤', '孩', '脱', '硫', '肥', '善', '龙', '演', '父', '渐', '血', '欢', '械', '掌', '歌', '沙', '刚', '攻', '谓', '盾', '讨', '晚', '粒', '乱', '燃', '矛', '乎', '杀', '药', '宁', '鲁', '贵', '钟', '煤', '读', '班', '伯', '香', '介', '迫', '句', '丰', '培', '握', '兰', '担', '弦', '蛋', '沉', '假', '穿', '执', '答', '乐', '谁', '顺', '烟', '缩', '征', '脸', '喜', '松', '脚', '困', '异', '免', '背', '星', '福', '买', '染', '井', '概', '慢', '怕', '磁', '倍', '祖', '皇', '促', '静', '补', '评', '翻', '肉', '践', '尼', '衣', '宽', '扬', '棉', '希', '伤', '操', '垂', '秋', '宜', '氢', '套', '督', '振', '架', '亮', '末', '宪', '庆', '编', '牛', '触', '映', '雷', '销', '诗', '座', '居', '抓', '裂', '胞', '呼', '娘', '景', '威', '绿', '晶', '厚', '盟', '衡', '鸡', '孙', '延', '危', '胶', '屋', '乡', '临', '陆', '顾', '掉', '呀', '灯', '岁', '措', '束', '耐', '剧', '玉', '赵', '跳', '哥', '季', '课', '凯', '胡', '额', '款', '绍', '卷', '齐', '伟', '蒸', '殖', '永', '宗', '苗', '川', '炉', '岩', '弱', '零', '杨', '奏', '沿', '露', '杆', '探', '滑', '镇', '饭', '浓', '航', '怀', '赶', '库', '夺', '伊', '灵', '税', '途', '灭', '赛', '归', '召', '鼓', '播', '盘', '裁', '险', '康', '唯', '录', '菌', '纯', '借', '糖', '盖', '横', '符', '私', '努', '堂', '域', '枪', '润', '幅', '哈', '竟', '熟', '虫', '泽', '脑', '壤', '碳', '欧', '遍', '侧', '寨', '敢', '彻', '虑', '斜', '薄', '庭', '纳', '弹', '饲', '伸', '折', '麦', '湿', '暗', '荷', '瓦', '塞', '床', '筑', '恶', '户', '访', '塔', '奇', '透', '梁', '刀', '旋', '迹', '卡', '氯', '遇', '份', '毒', '泥', '退', '洗', '摆', '灰', '彩', '卖', '耗', '夏', '择', '忙', '铜', '献', '硬', '予', '繁', '圈', '雪', '函', '亦', '抽', '篇', '阵', '阴', '丁', '尺', '追', '堆', '雄', '迎', '泛', '爸', '楼', '避', '谋', '吨', '野', '猪', '旗', '累', '偏', '典', '馆', '索', '秦', '脂', '潮', '爷', '豆', '忽', '托', '惊', '塑', '遗', '愈', '朱', '替', '纤', '粗', '倾', '尚', '痛', '楚', '谢', '奋', '购', '磨', '君', '池', '旁', '碎', '骨', '监', '捕', '弟', '暴', '割', '贯', '殊', '释', '词', '亡', '壁', '顿', '宝', '午', '尘', '闻', '揭', '炮', '残', '冬', '桥', '妇', '警', '综', '招', '吴', '付', '浮', '遭', '徐', '您', '摇', '谷', '赞', '箱', '隔', '订', '男', '吹', '园', '纷', '唐', '败', '宋', '玻', '巨', '耕', '坦', '荣', '闭', '湾', '键', '凡', '驻', '锅', '救', '恩', '剥', '凝', '碱', '齿', '截', '炼', '麻', '纺', '禁', '废', '盛', '版', '缓', '净', '睛', '昌', '婚', '涉', '筒', '嘴', '插', '岸', '朗', '庄', '街', '藏', '姑', '贸', '腐', '奴', '啦', '惯', '乘', '伙', '恢', '匀', '纱', '扎', '辩', '耳', '彪', '臣', '亿', '璃', '抵', '脉', '秀', '萨', '俄', '网', '舞', '店', '喷', '纵', '寸', '汗', '挂', '洪', '贺', '闪', '柬', '爆', '烯', '津', '稻', '墙', '软', '勇', '像', '滚', '厘', '蒙', '芳', '肯', '坡', '柱', '荡', '腿', '仪', '旅', '尾', '轧', '冰', '贡', '登', '黎', '削', '钻', '勒', '逃', '障', '氨', '郭', '峰', '币', '港', '伏', '轨', '亩', '毕', '擦', '莫', '刺', '浪', '秘', '援', '株', '健', '售', '股', '岛', '甘', '泡', '睡', '童', '铸', '汤', '阀', '休', '汇', '舍', '牧', '绕', '炸', '哲', '磷', '绩', '朋', '淡', '尖', '启', '陷', '柴', '呈', '徒', '颜', '泪', '稍', '忘', '泵', '蓝', '拖', '洞', '授', '镜', '辛', '壮', '锋', '贫', '虚', '弯', '摩', '泰', '幼', '廷', '尊', '窗', '纲', '弄', '隶', '疑', '氏', '宫', '姐', '震', '瑞', '怪', '尤', '琴', '循', '描', '膜', '违', '夹', '腰', '缘', '珠', '穷', '森', '枝', '竹', '沟', '催', '绳', '忆', '邦', '剩', '幸', '浆', '栏', '拥', '牙', '贮', '礼', '滤', '钠', '纹', '罢', '拍', '咱', '喊', '袖', '埃', '勤', '罚', '焦', '潜', '伍', '墨', '欲', '缝', '姓', '刊', '饱', '仿', '奖', '铝', '鬼', '丽', '跨', '默', '挖', '链', '扫', '喝', '袋', '炭', '污', '幕', '诸', '弧', '励', '梅', '奶', '洁', '灾', '舟', '鉴', '苯', '讼', '抱', '毁', '懂', '寒', '智', '埔', '寄', '届', '跃', '渡', '挑', '丹', '艰', '贝', '碰', '拔', '爹', '戴', '码', '梦', '芽', '熔', '赤', '渔', '哭', '敬', '颗', '奔', '铅', '仲', '虎', '稀', '妹', '乏', '珍', '申', '桌', '遵', '允', '隆', '螺', '仓', '魏', '锐', '晓', '氮', '兼', '隐', '碍', '赫', '拨', '忠', '肃', '缸', '牵', '抢', '博', '巧', '壳', '兄', '杜', '讯', '诚', '碧', '祥', '柯', '页', '巡', '矩', '悲', '灌', '龄', '伦', '票', '寻', '桂', '铺', '圣', '恐', '恰', '郑', '趣', '抬', '荒', '腾', '贴', '柔', '滴', '猛', '阔', '辆', '妻', '填', '撤', '储', '签', '闹', '扰', '紫', '砂', '递', '戏', '吊', '陶', '伐', '喂', '疗', '瓶', '婆', '抚', '臂', '摸', '忍', '虾', '蜡', '邻', '胸', '巩', '挤', '偶', '弃', '槽', '劲', '乳', '邓', '吉', '仁', '烂', '砖', '租', '乌', '舰', '伴', '瓜', '浅', '丙', '暂', '燥', '橡', '柳', '迷', '暖', '牌', '秧', '胆', '详', '簧', '踏', '瓷', '谱', '呆', '宾', '糊', '洛', '辉', '愤', '竞', '隙', '怒', '粘', '乃', '绪', '肩', '籍', '敏', '涂', '熙', '皆', '侦', '悬', '掘', '享', '纠', '醒', '狂', '锁', '淀', '恨', '牲', '霸', '爬', '赏', '逆', '玩', '陵', '祝', '秒', '浙', '貌', '役', '彼', '悉', '鸭', '趋', '凤', '晨', '畜', '辈', '秩', '卵', '署', '梯', '炎', '滩', '棋', '驱', '筛', '峡', '冒', '啥', '寿', '译', '浸', '泉', '帽', '迟', '硅', '疆', '贷', '漏', '稿', '冠', '嫩', '胁', '芯', '牢', '叛', '蚀', '奥', '鸣', '岭', '羊', '凭', '串', '塘', '绘', '酵', '融', '盆', '锡', '庙', '筹', '冻', '辅', '摄', '袭', '筋', '拒', '僚', '旱', '钾', '鸟', '漆', '沈', '眉', '疏', '添', '棒', '穗', '硝', '韩', '逼', '扭', '侨', '凉', '挺', '碗', '栽', '炒', '杯', '患', '馏', '劝', '豪', '辽', '勃', '鸿', '旦', '吏', '拜', '狗', '埋', '辊', '掩', '饮', '搬', '骂', '辞', '勾', '扣', '估', '蒋', '绒', '雾', '丈', '朵', '姆', '拟', '宇', '辑', '陕', '雕', '偿', '蓄', '崇', '剪', '倡', '厅', '咬', '驶', '薯', '刷', '斥', '番', '赋', '奉', '佛', '浇', '漫', '曼', '扇', '钙', '桃', '扶', '仔', '返', '俗', '亏', '腔', '鞋', '棱', '覆', '框', '悄', '叔', '撞', '骗', '勘', '旺', '沸', '孤', '吐', '孟', '渠', '屈', '疾', '妙', '惜', '仰', '狠', '胀', '谐', '抛', '霉', '桑', '岗', '嘛', '衰', '盗', '渗', '脏', '赖', '涌', '甜', '曹', '阅', '肌', '哩', '厉', '烃', '纬', '毅', '昨', '伪', '症', '煮', '叹', '钉', '搭', '茎', '笼', '酷', '偷', '弓', '锥', '恒', '杰', '坑', '鼻', '翼', '纶', '叙', '狱', '逮', '罐', '络', '棚', '抑', '膨', '蔬', '寺', '骤', '穆', '冶', '枯', '册', '尸', '凸', '绅', '坯', '牺', '焰', '轰', '欣', '晋', '瘦', '御', '锭', '锦', '丧', '旬', '锻', '垄', '搜', '扑', '邀', '亭', '酯', '迈', '舒', '脆', '酶', '闲', '忧', '酚', '顽', '羽', '涨', '卸', '仗', '陪', '辟', '惩', '杭', '姚', '肚', '捉', '飘', '漂', '昆', '欺', '吾', '郎', '烷', '汁', '呵', '饰', '萧', '雅', '邮', '迁', '燕', '撒', '姻', '赴', '宴', '烦', '债', '帐', '斑', '铃', '旨', '醇', '董', '饼', '雏', '姿', '拌', '傅', '腹', '妥', '揉', '贤', '拆', '歪', '葡', '胺', '丢', '浩', '徽', '昂', '垫', '挡', '览', '贪', '慰', '缴', '汪', '慌', '冯', '诺', '姜', '谊', '凶', '劣', '诬', '耀', '昏', '躺', '盈', '骑', '乔', '溪', '丛', '卢', '抹', '闷', '咨', '刮', '驾', '缆', '悟', '摘', '铒', '掷', '颇', '幻', '柄', '惠', '惨', '佳', '仇', '腊', '窝', '涤', '剑', '瞧', '堡', '泼', '葱', '罩', '霍', '捞', '胎', '苍', '滨', '俩', '捅', '湘', '砍', '霞', '邵', '萄', '疯', '淮', '遂', '熊', '粪', '烘', '宿', '档', '戈', '驳', '嫂', '裕', '徙', '箭', '捐', '肠', '撑', '晒', '辨', '殿', '莲', '摊', '搅', '酱', '屏', '疫', '哀', '蔡', '堵', '沫', '皱', '畅', '叠', '阁', '莱', '敲', '辖', '钩', '痕', '坝', '巷', '饿', '祸', '丘', '玄', '溜', '曰', '逻', '彭', '尝', '卿', '妨', '艇', '吞', '韦', '怨', '矮', '歇']; + +export default chinese; \ No newline at end of file diff --git a/src/backend/wallet/lib/words/english.js b/src/backend/wallet/lib/words/english.js new file mode 100644 index 0000000..a2cb079 --- /dev/null +++ b/src/backend/wallet/lib/words/english.js @@ -0,0 +1,5 @@ +'use strict'; + +var english = ['abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract', 'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid', 'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual', 'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance', 'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'age', 'agent', 'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album', 'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone', 'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among', 'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry', 'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique', 'anxiety', 'any', 'apart', 'apology', 'appear', 'apple', 'approve', 'april', 'arch', 'arctic', 'area', 'arena', 'argue', 'arm', 'armed', 'armor', 'army', 'around', 'arrange', 'arrest', 'arrive', 'arrow', 'art', 'artefact', 'artist', 'artwork', 'ask', 'aspect', 'assault', 'asset', 'assist', 'assume', 'asthma', 'athlete', 'atom', 'attack', 'attend', 'attitude', 'attract', 'auction', 'audit', 'august', 'aunt', 'author', 'auto', 'autumn', 'average', 'avocado', 'avoid', 'awake', 'aware', 'away', 'awesome', 'awful', 'awkward', 'axis', 'baby', 'bachelor', 'bacon', 'badge', 'bag', 'balance', 'balcony', 'ball', 'bamboo', 'banana', 'banner', 'bar', 'barely', 'bargain', 'barrel', 'base', 'basic', 'basket', 'battle', 'beach', 'bean', 'beauty', 'because', 'become', 'beef', 'before', 'begin', 'behave', 'behind', 'believe', 'below', 'belt', 'bench', 'benefit', 'best', 'betray', 'better', 'between', 'beyond', 'bicycle', 'bid', 'bike', 'bind', 'biology', 'bird', 'birth', 'bitter', 'black', 'blade', 'blame', 'blanket', 'blast', 'bleak', 'bless', 'blind', 'blood', 'blossom', 'blouse', 'blue', 'blur', 'blush', 'board', 'boat', 'body', 'boil', 'bomb', 'bone', 'bonus', 'book', 'boost', 'border', 'boring', 'borrow', 'boss', 'bottom', 'bounce', 'box', 'boy', 'bracket', 'brain', 'brand', 'brass', 'brave', 'bread', 'breeze', 'brick', 'bridge', 'brief', 'bright', 'bring', 'brisk', 'broccoli', 'broken', 'bronze', 'broom', 'brother', 'brown', 'brush', 'bubble', 'buddy', 'budget', 'buffalo', 'build', 'bulb', 'bulk', 'bullet', 'bundle', 'bunker', 'burden', 'burger', 'burst', 'bus', 'business', 'busy', 'butter', 'buyer', 'buzz', 'cabbage', 'cabin', 'cable', 'cactus', 'cage', 'cake', 'call', 'calm', 'camera', 'camp', 'can', 'canal', 'cancel', 'candy', 'cannon', 'canoe', 'canvas', 'canyon', 'capable', 'capital', 'captain', 'car', 'carbon', 'card', 'cargo', 'carpet', 'carry', 'cart', 'case', 'cash', 'casino', 'castle', 'casual', 'cat', 'catalog', 'catch', 'category', 'cattle', 'caught', 'cause', 'caution', 'cave', 'ceiling', 'celery', 'cement', 'census', 'century', 'cereal', 'certain', 'chair', 'chalk', 'champion', 'change', 'chaos', 'chapter', 'charge', 'chase', 'chat', 'cheap', 'check', 'cheese', 'chef', 'cherry', 'chest', 'chicken', 'chief', 'child', 'chimney', 'choice', 'choose', 'chronic', 'chuckle', 'chunk', 'churn', 'cigar', 'cinnamon', 'circle', 'citizen', 'city', 'civil', 'claim', 'clap', 'clarify', 'claw', 'clay', 'clean', 'clerk', 'clever', 'click', 'client', 'cliff', 'climb', 'clinic', 'clip', 'clock', 'clog', 'close', 'cloth', 'cloud', 'clown', 'club', 'clump', 'cluster', 'clutch', 'coach', 'coast', 'coconut', 'code', 'coffee', 'coil', 'coin', 'collect', 'color', 'column', 'combine', 'come', 'comfort', 'comic', 'common', 'company', 'concert', 'conduct', 'confirm', 'congress', 'connect', 'consider', 'control', 'convince', 'cook', 'cool', 'copper', 'copy', 'coral', 'core', 'corn', 'correct', 'cost', 'cotton', 'couch', 'country', 'couple', 'course', 'cousin', 'cover', 'coyote', 'crack', 'cradle', 'craft', 'cram', 'crane', 'crash', 'crater', 'crawl', 'crazy', 'cream', 'credit', 'creek', 'crew', 'cricket', 'crime', 'crisp', 'critic', 'crop', 'cross', 'crouch', 'crowd', 'crucial', 'cruel', 'cruise', 'crumble', 'crunch', 'crush', 'cry', 'crystal', 'cube', 'culture', 'cup', 'cupboard', 'curious', 'current', 'curtain', 'curve', 'cushion', 'custom', 'cute', 'cycle', 'dad', 'damage', 'damp', 'dance', 'danger', 'daring', 'dash', 'daughter', 'dawn', 'day', 'deal', 'debate', 'debris', 'decade', 'december', 'decide', 'decline', 'decorate', 'decrease', 'deer', 'defense', 'define', 'defy', 'degree', 'delay', 'deliver', 'demand', 'demise', 'denial', 'dentist', 'deny', 'depart', 'depend', 'deposit', 'depth', 'deputy', 'derive', 'describe', 'desert', 'design', 'desk', 'despair', 'destroy', 'detail', 'detect', 'develop', 'device', 'devote', 'diagram', 'dial', 'diamond', 'diary', 'dice', 'diesel', 'diet', 'differ', 'digital', 'dignity', 'dilemma', 'dinner', 'dinosaur', 'direct', 'dirt', 'disagree', 'discover', 'disease', 'dish', 'dismiss', 'disorder', 'display', 'distance', 'divert', 'divide', 'divorce', 'dizzy', 'doctor', 'document', 'dog', 'doll', 'dolphin', 'domain', 'donate', 'donkey', 'donor', 'door', 'dose', 'double', 'dove', 'draft', 'dragon', 'drama', 'drastic', 'draw', 'dream', 'dress', 'drift', 'drill', 'drink', 'drip', 'drive', 'drop', 'drum', 'dry', 'duck', 'dumb', 'dune', 'during', 'dust', 'dutch', 'duty', 'dwarf', 'dynamic', 'eager', 'eagle', 'early', 'earn', 'earth', 'easily', 'east', 'easy', 'echo', 'ecology', 'economy', 'edge', 'edit', 'educate', 'effort', 'egg', 'eight', 'either', 'elbow', 'elder', 'electric', 'elegant', 'element', 'elephant', 'elevator', 'elite', 'else', 'embark', 'embody', 'embrace', 'emerge', 'emotion', 'employ', 'empower', 'empty', 'enable', 'enact', 'end', 'endless', 'endorse', 'enemy', 'energy', 'enforce', 'engage', 'engine', 'enhance', 'enjoy', 'enlist', 'enough', 'enrich', 'enroll', 'ensure', 'enter', 'entire', 'entry', 'envelope', 'episode', 'equal', 'equip', 'era', 'erase', 'erode', 'erosion', 'error', 'erupt', 'escape', 'essay', 'essence', 'estate', 'eternal', 'ethics', 'evidence', 'evil', 'evoke', 'evolve', 'exact', 'example', 'excess', 'exchange', 'excite', 'exclude', 'excuse', 'execute', 'exercise', 'exhaust', 'exhibit', 'exile', 'exist', 'exit', 'exotic', 'expand', 'expect', 'expire', 'explain', 'expose', 'express', 'extend', 'extra', 'eye', 'eyebrow', 'fabric', 'face', 'faculty', 'fade', 'faint', 'faith', 'fall', 'false', 'fame', 'family', 'famous', 'fan', 'fancy', 'fantasy', 'farm', 'fashion', 'fat', 'fatal', 'father', 'fatigue', 'fault', 'favorite', 'feature', 'february', 'federal', 'fee', 'feed', 'feel', 'female', 'fence', 'festival', 'fetch', 'fever', 'few', 'fiber', 'fiction', 'field', 'figure', 'file', 'film', 'filter', 'final', 'find', 'fine', 'finger', 'finish', 'fire', 'firm', 'first', 'fiscal', 'fish', 'fit', 'fitness', 'fix', 'flag', 'flame', 'flash', 'flat', 'flavor', 'flee', 'flight', 'flip', 'float', 'flock', 'floor', 'flower', 'fluid', 'flush', 'fly', 'foam', 'focus', 'fog', 'foil', 'fold', 'follow', 'food', 'foot', 'force', 'forest', 'forget', 'fork', 'fortune', 'forum', 'forward', 'fossil', 'foster', 'found', 'fox', 'fragile', 'frame', 'frequent', 'fresh', 'friend', 'fringe', 'frog', 'front', 'frost', 'frown', 'frozen', 'fruit', 'fuel', 'fun', 'funny', 'furnace', 'fury', 'future', 'gadget', 'gain', 'galaxy', 'gallery', 'game', 'gap', 'garage', 'garbage', 'garden', 'garlic', 'garment', 'gas', 'gasp', 'gate', 'gather', 'gauge', 'gaze', 'general', 'genius', 'genre', 'gentle', 'genuine', 'gesture', 'ghost', 'giant', 'gift', 'giggle', 'ginger', 'giraffe', 'girl', 'give', 'glad', 'glance', 'glare', 'glass', 'glide', 'glimpse', 'globe', 'gloom', 'glory', 'glove', 'glow', 'glue', 'goat', 'goddess', 'gold', 'good', 'goose', 'gorilla', 'gospel', 'gossip', 'govern', 'gown', 'grab', 'grace', 'grain', 'grant', 'grape', 'grass', 'gravity', 'great', 'green', 'grid', 'grief', 'grit', 'grocery', 'group', 'grow', 'grunt', 'guard', 'guess', 'guide', 'guilt', 'guitar', 'gun', 'gym', 'habit', 'hair', 'half', 'hammer', 'hamster', 'hand', 'happy', 'harbor', 'hard', 'harsh', 'harvest', 'hat', 'have', 'hawk', 'hazard', 'head', 'health', 'heart', 'heavy', 'hedgehog', 'height', 'hello', 'helmet', 'help', 'hen', 'hero', 'hidden', 'high', 'hill', 'hint', 'hip', 'hire', 'history', 'hobby', 'hockey', 'hold', 'hole', 'holiday', 'hollow', 'home', 'honey', 'hood', 'hope', 'horn', 'horror', 'horse', 'hospital', 'host', 'hotel', 'hour', 'hover', 'hub', 'huge', 'human', 'humble', 'humor', 'hundred', 'hungry', 'hunt', 'hurdle', 'hurry', 'hurt', 'husband', 'hybrid', 'ice', 'icon', 'idea', 'identify', 'idle', 'ignore', 'ill', 'illegal', 'illness', 'image', 'imitate', 'immense', 'immune', 'impact', 'impose', 'improve', 'impulse', 'inch', 'include', 'income', 'increase', 'index', 'indicate', 'indoor', 'industry', 'infant', 'inflict', 'inform', 'inhale', 'inherit', 'initial', 'inject', 'injury', 'inmate', 'inner', 'innocent', 'input', 'inquiry', 'insane', 'insect', 'inside', 'inspire', 'install', 'intact', 'interest', 'into', 'invest', 'invite', 'involve', 'iron', 'island', 'isolate', 'issue', 'item', 'ivory', 'jacket', 'jaguar', 'jar', 'jazz', 'jealous', 'jeans', 'jelly', 'jewel', 'job', 'join', 'joke', 'journey', 'joy', 'judge', 'juice', 'jump', 'jungle', 'junior', 'junk', 'just', 'kangaroo', 'keen', 'keep', 'ketchup', 'key', 'kick', 'kid', 'kidney', 'kind', 'kingdom', 'kiss', 'kit', 'kitchen', 'kite', 'kitten', 'kiwi', 'knee', 'knife', 'knock', 'know', 'lab', 'label', 'labor', 'ladder', 'lady', 'lake', 'lamp', 'language', 'laptop', 'large', 'later', 'latin', 'laugh', 'laundry', 'lava', 'law', 'lawn', 'lawsuit', 'layer', 'lazy', 'leader', 'leaf', 'learn', 'leave', 'lecture', 'left', 'leg', 'legal', 'legend', 'leisure', 'lemon', 'lend', 'length', 'lens', 'leopard', 'lesson', 'letter', 'level', 'liar', 'liberty', 'library', 'license', 'life', 'lift', 'light', 'like', 'limb', 'limit', 'link', 'lion', 'liquid', 'list', 'little', 'live', 'lizard', 'load', 'loan', 'lobster', 'local', 'lock', 'logic', 'lonely', 'long', 'loop', 'lottery', 'loud', 'lounge', 'love', 'loyal', 'lucky', 'luggage', 'lumber', 'lunar', 'lunch', 'luxury', 'lyrics', 'machine', 'mad', 'magic', 'magnet', 'maid', 'mail', 'main', 'major', 'make', 'mammal', 'man', 'manage', 'mandate', 'mango', 'mansion', 'manual', 'maple', 'marble', 'march', 'margin', 'marine', 'market', 'marriage', 'mask', 'mass', 'master', 'match', 'material', 'math', 'matrix', 'matter', 'maximum', 'maze', 'meadow', 'mean', 'measure', 'meat', 'mechanic', 'medal', 'media', 'melody', 'melt', 'member', 'memory', 'mention', 'menu', 'mercy', 'merge', 'merit', 'merry', 'mesh', 'message', 'metal', 'method', 'middle', 'midnight', 'milk', 'million', 'mimic', 'mind', 'minimum', 'minor', 'minute', 'miracle', 'mirror', 'misery', 'miss', 'mistake', 'mix', 'mixed', 'mixture', 'mobile', 'model', 'modify', 'mom', 'moment', 'monitor', 'monkey', 'monster', 'month', 'moon', 'moral', 'more', 'morning', 'mosquito', 'mother', 'motion', 'motor', 'mountain', 'mouse', 'move', 'movie', 'much', 'muffin', 'mule', 'multiply', 'muscle', 'museum', 'mushroom', 'music', 'must', 'mutual', 'myself', 'mystery', 'myth', 'naive', 'name', 'napkin', 'narrow', 'nasty', 'nation', 'nature', 'near', 'neck', 'need', 'negative', 'neglect', 'neither', 'nephew', 'nerve', 'nest', 'net', 'network', 'neutral', 'never', 'news', 'next', 'nice', 'night', 'noble', 'noise', 'nominee', 'noodle', 'normal', 'north', 'nose', 'notable', 'note', 'nothing', 'notice', 'novel', 'now', 'nuclear', 'number', 'nurse', 'nut', 'oak', 'obey', 'object', 'oblige', 'obscure', 'observe', 'obtain', 'obvious', 'occur', 'ocean', 'october', 'odor', 'off', 'offer', 'office', 'often', 'oil', 'okay', 'old', 'olive', 'olympic', 'omit', 'once', 'one', 'onion', 'online', 'only', 'open', 'opera', 'opinion', 'oppose', 'option', 'orange', 'orbit', 'orchard', 'order', 'ordinary', 'organ', 'orient', 'original', 'orphan', 'ostrich', 'other', 'outdoor', 'outer', 'output', 'outside', 'oval', 'oven', 'over', 'own', 'owner', 'oxygen', 'oyster', 'ozone', 'pact', 'paddle', 'page', 'pair', 'palace', 'palm', 'panda', 'panel', 'panic', 'panther', 'paper', 'parade', 'parent', 'park', 'parrot', 'party', 'pass', 'patch', 'path', 'patient', 'patrol', 'pattern', 'pause', 'pave', 'payment', 'peace', 'peanut', 'pear', 'peasant', 'pelican', 'pen', 'penalty', 'pencil', 'people', 'pepper', 'perfect', 'permit', 'person', 'pet', 'phone', 'photo', 'phrase', 'physical', 'piano', 'picnic', 'picture', 'piece', 'pig', 'pigeon', 'pill', 'pilot', 'pink', 'pioneer', 'pipe', 'pistol', 'pitch', 'pizza', 'place', 'planet', 'plastic', 'plate', 'play', 'please', 'pledge', 'pluck', 'plug', 'plunge', 'poem', 'poet', 'point', 'polar', 'pole', 'police', 'pond', 'pony', 'pool', 'popular', 'portion', 'position', 'possible', 'post', 'potato', 'pottery', 'poverty', 'powder', 'power', 'practice', 'praise', 'predict', 'prefer', 'prepare', 'present', 'pretty', 'prevent', 'price', 'pride', 'primary', 'print', 'priority', 'prison', 'private', 'prize', 'problem', 'process', 'produce', 'profit', 'program', 'project', 'promote', 'proof', 'property', 'prosper', 'protect', 'proud', 'provide', 'public', 'pudding', 'pull', 'pulp', 'pulse', 'pumpkin', 'punch', 'pupil', 'puppy', 'purchase', 'purity', 'purpose', 'purse', 'push', 'put', 'puzzle', 'pyramid', 'quality', 'quantum', 'quarter', 'question', 'quick', 'quit', 'quiz', 'quote', 'rabbit', 'raccoon', 'race', 'rack', 'radar', 'radio', 'rail', 'rain', 'raise', 'rally', 'ramp', 'ranch', 'random', 'range', 'rapid', 'rare', 'rate', 'rather', 'raven', 'raw', 'razor', 'ready', 'real', 'reason', 'rebel', 'rebuild', 'recall', 'receive', 'recipe', 'record', 'recycle', 'reduce', 'reflect', 'reform', 'refuse', 'region', 'regret', 'regular', 'reject', 'relax', 'release', 'relief', 'rely', 'remain', 'remember', 'remind', 'remove', 'render', 'renew', 'rent', 'reopen', 'repair', 'repeat', 'replace', 'report', 'require', 'rescue', 'resemble', 'resist', 'resource', 'response', 'result', 'retire', 'retreat', 'return', 'reunion', 'reveal', 'review', 'reward', 'rhythm', 'rib', 'ribbon', 'rice', 'rich', 'ride', 'ridge', 'rifle', 'right', 'rigid', 'ring', 'riot', 'ripple', 'risk', 'ritual', 'rival', 'river', 'road', 'roast', 'robot', 'robust', 'rocket', 'romance', 'roof', 'rookie', 'room', 'rose', 'rotate', 'rough', 'round', 'route', 'royal', 'rubber', 'rude', 'rug', 'rule', 'run', 'runway', 'rural', 'sad', 'saddle', 'sadness', 'safe', 'sail', 'salad', 'salmon', 'salon', 'salt', 'salute', 'same', 'sample', 'sand', 'satisfy', 'satoshi', 'sauce', 'sausage', 'save', 'say', 'scale', 'scan', 'scare', 'scatter', 'scene', 'scheme', 'school', 'science', 'scissors', 'scorpion', 'scout', 'scrap', 'screen', 'script', 'scrub', 'sea', 'search', 'season', 'seat', 'second', 'secret', 'section', 'security', 'seed', 'seek', 'segment', 'select', 'sell', 'seminar', 'senior', 'sense', 'sentence', 'series', 'service', 'session', 'settle', 'setup', 'seven', 'shadow', 'shaft', 'shallow', 'share', 'shed', 'shell', 'sheriff', 'shield', 'shift', 'shine', 'ship', 'shiver', 'shock', 'shoe', 'shoot', 'shop', 'short', 'shoulder', 'shove', 'shrimp', 'shrug', 'shuffle', 'shy', 'sibling', 'sick', 'side', 'siege', 'sight', 'sign', 'silent', 'silk', 'silly', 'silver', 'similar', 'simple', 'since', 'sing', 'siren', 'sister', 'situate', 'six', 'size', 'skate', 'sketch', 'ski', 'skill', 'skin', 'skirt', 'skull', 'slab', 'slam', 'sleep', 'slender', 'slice', 'slide', 'slight', 'slim', 'slogan', 'slot', 'slow', 'slush', 'small', 'smart', 'smile', 'smoke', 'smooth', 'snack', 'snake', 'snap', 'sniff', 'snow', 'soap', 'soccer', 'social', 'sock', 'soda', 'soft', 'solar', 'soldier', 'solid', 'solution', 'solve', 'someone', 'song', 'soon', 'sorry', 'sort', 'soul', 'sound', 'soup', 'source', 'south', 'space', 'spare', 'spatial', 'spawn', 'speak', 'special', 'speed', 'spell', 'spend', 'sphere', 'spice', 'spider', 'spike', 'spin', 'spirit', 'split', 'spoil', 'sponsor', 'spoon', 'sport', 'spot', 'spray', 'spread', 'spring', 'spy', 'square', 'squeeze', 'squirrel', 'stable', 'stadium', 'staff', 'stage', 'stairs', 'stamp', 'stand', 'start', 'state', 'stay', 'steak', 'steel', 'stem', 'step', 'stereo', 'stick', 'still', 'sting', 'stock', 'stomach', 'stone', 'stool', 'story', 'stove', 'strategy', 'street', 'strike', 'strong', 'struggle', 'student', 'stuff', 'stumble', 'style', 'subject', 'submit', 'subway', 'success', 'such', 'sudden', 'suffer', 'sugar', 'suggest', 'suit', 'summer', 'sun', 'sunny', 'sunset', 'super', 'supply', 'supreme', 'sure', 'surface', 'surge', 'surprise', 'surround', 'survey', 'suspect', 'sustain', 'swallow', 'swamp', 'swap', 'swarm', 'swear', 'sweet', 'swift', 'swim', 'swing', 'switch', 'sword', 'symbol', 'symptom', 'syrup', 'system', 'table', 'tackle', 'tag', 'tail', 'talent', 'talk', 'tank', 'tape', 'target', 'task', 'taste', 'tattoo', 'taxi', 'teach', 'team', 'tell', 'ten', 'tenant', 'tennis', 'tent', 'term', 'test', 'text', 'thank', 'that', 'theme', 'then', 'theory', 'there', 'they', 'thing', 'this', 'thought', 'three', 'thrive', 'throw', 'thumb', 'thunder', 'ticket', 'tide', 'tiger', 'tilt', 'timber', 'time', 'tiny', 'tip', 'tired', 'tissue', 'title', 'toast', 'tobacco', 'today', 'toddler', 'toe', 'together', 'toilet', 'token', 'tomato', 'tomorrow', 'tone', 'tongue', 'tonight', 'tool', 'tooth', 'top', 'topic', 'topple', 'torch', 'tornado', 'tortoise', 'toss', 'total', 'tourist', 'toward', 'tower', 'town', 'toy', 'track', 'trade', 'traffic', 'tragic', 'train', 'transfer', 'trap', 'trash', 'travel', 'tray', 'treat', 'tree', 'trend', 'trial', 'tribe', 'trick', 'trigger', 'trim', 'trip', 'trophy', 'trouble', 'truck', 'true', 'truly', 'trumpet', 'trust', 'truth', 'try', 'tube', 'tuition', 'tumble', 'tuna', 'tunnel', 'turkey', 'turn', 'turtle', 'twelve', 'twenty', 'twice', 'twin', 'twist', 'two', 'type', 'typical', 'ugly', 'umbrella', 'unable', 'unaware', 'uncle', 'uncover', 'under', 'undo', 'unfair', 'unfold', 'unhappy', 'uniform', 'unique', 'unit', 'universe', 'unknown', 'unlock', 'until', 'unusual', 'unveil', 'update', 'upgrade', 'uphold', 'upon', 'upper', 'upset', 'urban', 'urge', 'usage', 'use', 'used', 'useful', 'useless', 'usual', 'utility', 'vacant', 'vacuum', 'vague', 'valid', 'valley', 'valve', 'van', 'vanish', 'vapor', 'various', 'vast', 'vault', 'vehicle', 'velvet', 'vendor', 'venture', 'venue', 'verb', 'verify', 'version', 'very', 'vessel', 'veteran', 'viable', 'vibrant', 'vicious', 'victory', 'video', 'view', 'village', 'vintage', 'violin', 'virtual', 'virus', 'visa', 'visit', 'visual', 'vital', 'vivid', 'vocal', 'voice', 'void', 'volcano', 'volume', 'vote', 'voyage', 'wage', 'wagon', 'wait', 'walk', 'wall', 'walnut', 'want', 'warfare', 'warm', 'warrior', 'wash', 'wasp', 'waste', 'water', 'wave', 'way', 'wealth', 'weapon', 'wear', 'weasel', 'weather', 'web', 'wedding', 'weekend', 'weird', 'welcome', 'west', 'wet', 'whale', 'what', 'wheat', 'wheel', 'when', 'where', 'whip', 'whisper', 'wide', 'width', 'wife', 'wild', 'will', 'win', 'window', 'wine', 'wing', 'wink', 'winner', 'winter', 'wire', 'wisdom', 'wise', 'wish', 'witness', 'wolf', 'woman', 'wonder', 'wood', 'wool', 'word', 'work', 'world', 'worry', 'worth', 'wrap', 'wreck', 'wrestle', 'wrist', 'write', 'wrong', 'yard', 'year', 'yellow', 'you', 'young', 'youth', 'zebra', 'zero', 'zone', 'zoo']; + +export default english; \ No newline at end of file diff --git a/src/backend/wallet/lib/words/french.js b/src/backend/wallet/lib/words/french.js new file mode 100644 index 0000000..be869a2 --- /dev/null +++ b/src/backend/wallet/lib/words/french.js @@ -0,0 +1,5 @@ +'use string'; + +var french = ['abaisser', 'abandon', 'abdiquer', 'abeille', 'abolir', 'aborder', 'aboutir', 'aboyer', 'abrasif', 'abreuver', 'abriter', 'abroger', 'abrupt', 'absence', 'absolu', 'absurde', 'abusif', 'abyssal', 'académie', 'acajou', 'acarien', 'accabler', 'accepter', 'acclamer', 'accolade', 'accroche', 'accuser', 'acerbe', 'achat', 'acheter', 'aciduler', 'acier', 'acompte', 'acquérir', 'acronyme', 'acteur', 'actif', 'actuel', 'adepte', 'adéquat', 'adhésif', 'adjectif', 'adjuger', 'admettre', 'admirer', 'adopter', 'adorer', 'adoucir', 'adresse', 'adroit', 'adulte', 'adverbe', 'aérer', 'aéronef', 'affaire', 'affecter', 'affiche', 'affreux', 'affubler', 'agacer', 'agencer', 'agile', 'agiter', 'agrafer', 'agréable', 'agrume', 'aider', 'aiguille', 'ailier', 'aimable', 'aisance', 'ajouter', 'ajuster', 'alarmer', 'alchimie', 'alerte', 'algèbre', 'algue', 'aliéner', 'aliment', 'alléger', 'alliage', 'allouer', 'allumer', 'alourdir', 'alpaga', 'altesse', 'alvéole', 'amateur', 'ambigu', 'ambre', 'aménager', 'amertume', 'amidon', 'amiral', 'amorcer', 'amour', 'amovible', 'amphibie', 'ampleur', 'amusant', 'analyse', 'anaphore', 'anarchie', 'anatomie', 'ancien', 'anéantir', 'angle', 'angoisse', 'anguleux', 'animal', 'annexer', 'annonce', 'annuel', 'anodin', 'anomalie', 'anonyme', 'anormal', 'antenne', 'antidote', 'anxieux', 'apaiser', 'apéritif', 'aplanir', 'apologie', 'appareil', 'appeler', 'apporter', 'appuyer', 'aquarium', 'aqueduc', 'arbitre', 'arbuste', 'ardeur', 'ardoise', 'argent', 'arlequin', 'armature', 'armement', 'armoire', 'armure', 'arpenter', 'arracher', 'arriver', 'arroser', 'arsenic', 'artériel', 'article', 'aspect', 'asphalte', 'aspirer', 'assaut', 'asservir', 'assiette', 'associer', 'assurer', 'asticot', 'astre', 'astuce', 'atelier', 'atome', 'atrium', 'atroce', 'attaque', 'attentif', 'attirer', 'attraper', 'aubaine', 'auberge', 'audace', 'audible', 'augurer', 'aurore', 'automne', 'autruche', 'avaler', 'avancer', 'avarice', 'avenir', 'averse', 'aveugle', 'aviateur', 'avide', 'avion', 'aviser', 'avoine', 'avouer', 'avril', 'axial', 'axiome', 'badge', 'bafouer', 'bagage', 'baguette', 'baignade', 'balancer', 'balcon', 'baleine', 'balisage', 'bambin', 'bancaire', 'bandage', 'banlieue', 'bannière', 'banquier', 'barbier', 'baril', 'baron', 'barque', 'barrage', 'bassin', 'bastion', 'bataille', 'bateau', 'batterie', 'baudrier', 'bavarder', 'belette', 'bélier', 'belote', 'bénéfice', 'berceau', 'berger', 'berline', 'bermuda', 'besace', 'besogne', 'bétail', 'beurre', 'biberon', 'bicycle', 'bidule', 'bijou', 'bilan', 'bilingue', 'billard', 'binaire', 'biologie', 'biopsie', 'biotype', 'biscuit', 'bison', 'bistouri', 'bitume', 'bizarre', 'blafard', 'blague', 'blanchir', 'blessant', 'blinder', 'blond', 'bloquer', 'blouson', 'bobard', 'bobine', 'boire', 'boiser', 'bolide', 'bonbon', 'bondir', 'bonheur', 'bonifier', 'bonus', 'bordure', 'borne', 'botte', 'boucle', 'boueux', 'bougie', 'boulon', 'bouquin', 'bourse', 'boussole', 'boutique', 'boxeur', 'branche', 'brasier', 'brave', 'brebis', 'brèche', 'breuvage', 'bricoler', 'brigade', 'brillant', 'brioche', 'brique', 'brochure', 'broder', 'bronzer', 'brousse', 'broyeur', 'brume', 'brusque', 'brutal', 'bruyant', 'buffle', 'buisson', 'bulletin', 'bureau', 'burin', 'bustier', 'butiner', 'butoir', 'buvable', 'buvette', 'cabanon', 'cabine', 'cachette', 'cadeau', 'cadre', 'caféine', 'caillou', 'caisson', 'calculer', 'calepin', 'calibre', 'calmer', 'calomnie', 'calvaire', 'camarade', 'caméra', 'camion', 'campagne', 'canal', 'caneton', 'canon', 'cantine', 'canular', 'capable', 'caporal', 'caprice', 'capsule', 'capter', 'capuche', 'carabine', 'carbone', 'caresser', 'caribou', 'carnage', 'carotte', 'carreau', 'carton', 'cascade', 'casier', 'casque', 'cassure', 'causer', 'caution', 'cavalier', 'caverne', 'caviar', 'cédille', 'ceinture', 'céleste', 'cellule', 'cendrier', 'censurer', 'central', 'cercle', 'cérébral', 'cerise', 'cerner', 'cerveau', 'cesser', 'chagrin', 'chaise', 'chaleur', 'chambre', 'chance', 'chapitre', 'charbon', 'chasseur', 'chaton', 'chausson', 'chavirer', 'chemise', 'chenille', 'chéquier', 'chercher', 'cheval', 'chien', 'chiffre', 'chignon', 'chimère', 'chiot', 'chlorure', 'chocolat', 'choisir', 'chose', 'chouette', 'chrome', 'chute', 'cigare', 'cigogne', 'cimenter', 'cinéma', 'cintrer', 'circuler', 'cirer', 'cirque', 'citerne', 'citoyen', 'citron', 'civil', 'clairon', 'clameur', 'claquer', 'classe', 'clavier', 'client', 'cligner', 'climat', 'clivage', 'cloche', 'clonage', 'cloporte', 'cobalt', 'cobra', 'cocasse', 'cocotier', 'coder', 'codifier', 'coffre', 'cogner', 'cohésion', 'coiffer', 'coincer', 'colère', 'colibri', 'colline', 'colmater', 'colonel', 'combat', 'comédie', 'commande', 'compact', 'concert', 'conduire', 'confier', 'congeler', 'connoter', 'consonne', 'contact', 'convexe', 'copain', 'copie', 'corail', 'corbeau', 'cordage', 'corniche', 'corpus', 'correct', 'cortège', 'cosmique', 'costume', 'coton', 'coude', 'coupure', 'courage', 'couteau', 'couvrir', 'coyote', 'crabe', 'crainte', 'cravate', 'crayon', 'créature', 'créditer', 'crémeux', 'creuser', 'crevette', 'cribler', 'crier', 'cristal', 'critère', 'croire', 'croquer', 'crotale', 'crucial', 'cruel', 'crypter', 'cubique', 'cueillir', 'cuillère', 'cuisine', 'cuivre', 'culminer', 'cultiver', 'cumuler', 'cupide', 'curatif', 'curseur', 'cyanure', 'cycle', 'cylindre', 'cynique', 'daigner', 'damier', 'danger', 'danseur', 'dauphin', 'débattre', 'débiter', 'déborder', 'débrider', 'débutant', 'décaler', 'décembre', 'déchirer', 'décider', 'déclarer', 'décorer', 'décrire', 'décupler', 'dédale', 'déductif', 'déesse', 'défensif', 'défiler', 'défrayer', 'dégager', 'dégivrer', 'déglutir', 'dégrafer', 'déjeuner', 'délice', 'déloger', 'demander', 'demeurer', 'démolir', 'dénicher', 'dénouer', 'dentelle', 'dénuder', 'départ', 'dépenser', 'déphaser', 'déplacer', 'déposer', 'déranger', 'dérober', 'désastre', 'descente', 'désert', 'désigner', 'désobéir', 'dessiner', 'destrier', 'détacher', 'détester', 'détourer', 'détresse', 'devancer', 'devenir', 'deviner', 'devoir', 'diable', 'dialogue', 'diamant', 'dicter', 'différer', 'digérer', 'digital', 'digne', 'diluer', 'dimanche', 'diminuer', 'dioxyde', 'directif', 'diriger', 'discuter', 'disposer', 'dissiper', 'distance', 'divertir', 'diviser', 'docile', 'docteur', 'dogme', 'doigt', 'domaine', 'domicile', 'dompter', 'donateur', 'donjon', 'donner', 'dopamine', 'dortoir', 'dorure', 'dosage', 'doseur', 'dossier', 'dotation', 'douanier', 'double', 'douceur', 'douter', 'doyen', 'dragon', 'draper', 'dresser', 'dribbler', 'droiture', 'duperie', 'duplexe', 'durable', 'durcir', 'dynastie', 'éblouir', 'écarter', 'écharpe', 'échelle', 'éclairer', 'éclipse', 'éclore', 'écluse', 'école', 'économie', 'écorce', 'écouter', 'écraser', 'écrémer', 'écrivain', 'écrou', 'écume', 'écureuil', 'édifier', 'éduquer', 'effacer', 'effectif', 'effigie', 'effort', 'effrayer', 'effusion', 'égaliser', 'égarer', 'éjecter', 'élaborer', 'élargir', 'électron', 'élégant', 'éléphant', 'élève', 'éligible', 'élitisme', 'éloge', 'élucider', 'éluder', 'emballer', 'embellir', 'embryon', 'émeraude', 'émission', 'emmener', 'émotion', 'émouvoir', 'empereur', 'employer', 'emporter', 'emprise', 'émulsion', 'encadrer', 'enchère', 'enclave', 'encoche', 'endiguer', 'endosser', 'endroit', 'enduire', 'énergie', 'enfance', 'enfermer', 'enfouir', 'engager', 'engin', 'englober', 'énigme', 'enjamber', 'enjeu', 'enlever', 'ennemi', 'ennuyeux', 'enrichir', 'enrobage', 'enseigne', 'entasser', 'entendre', 'entier', 'entourer', 'entraver', 'énumérer', 'envahir', 'enviable', 'envoyer', 'enzyme', 'éolien', 'épaissir', 'épargne', 'épatant', 'épaule', 'épicerie', 'épidémie', 'épier', 'épilogue', 'épine', 'épisode', 'épitaphe', 'époque', 'épreuve', 'éprouver', 'épuisant', 'équerre', 'équipe', 'ériger', 'érosion', 'erreur', 'éruption', 'escalier', 'espadon', 'espèce', 'espiègle', 'espoir', 'esprit', 'esquiver', 'essayer', 'essence', 'essieu', 'essorer', 'estime', 'estomac', 'estrade', 'étagère', 'étaler', 'étanche', 'étatique', 'éteindre', 'étendoir', 'éternel', 'éthanol', 'éthique', 'ethnie', 'étirer', 'étoffer', 'étoile', 'étonnant', 'étourdir', 'étrange', 'étroit', 'étude', 'euphorie', 'évaluer', 'évasion', 'éventail', 'évidence', 'éviter', 'évolutif', 'évoquer', 'exact', 'exagérer', 'exaucer', 'exceller', 'excitant', 'exclusif', 'excuse', 'exécuter', 'exemple', 'exercer', 'exhaler', 'exhorter', 'exigence', 'exiler', 'exister', 'exotique', 'expédier', 'explorer', 'exposer', 'exprimer', 'exquis', 'extensif', 'extraire', 'exulter', 'fable', 'fabuleux', 'facette', 'facile', 'facture', 'faiblir', 'falaise', 'fameux', 'famille', 'farceur', 'farfelu', 'farine', 'farouche', 'fasciner', 'fatal', 'fatigue', 'faucon', 'fautif', 'faveur', 'favori', 'fébrile', 'féconder', 'fédérer', 'félin', 'femme', 'fémur', 'fendoir', 'féodal', 'fermer', 'féroce', 'ferveur', 'festival', 'feuille', 'feutre', 'février', 'fiasco', 'ficeler', 'fictif', 'fidèle', 'figure', 'filature', 'filetage', 'filière', 'filleul', 'filmer', 'filou', 'filtrer', 'financer', 'finir', 'fiole', 'firme', 'fissure', 'fixer', 'flairer', 'flamme', 'flasque', 'flatteur', 'fléau', 'flèche', 'fleur', 'flexion', 'flocon', 'flore', 'fluctuer', 'fluide', 'fluvial', 'folie', 'fonderie', 'fongible', 'fontaine', 'forcer', 'forgeron', 'formuler', 'fortune', 'fossile', 'foudre', 'fougère', 'fouiller', 'foulure', 'fourmi', 'fragile', 'fraise', 'franchir', 'frapper', 'frayeur', 'frégate', 'freiner', 'frelon', 'frémir', 'frénésie', 'frère', 'friable', 'friction', 'frisson', 'frivole', 'froid', 'fromage', 'frontal', 'frotter', 'fruit', 'fugitif', 'fuite', 'fureur', 'furieux', 'furtif', 'fusion', 'futur', 'gagner', 'galaxie', 'galerie', 'gambader', 'garantir', 'gardien', 'garnir', 'garrigue', 'gazelle', 'gazon', 'géant', 'gélatine', 'gélule', 'gendarme', 'général', 'génie', 'genou', 'gentil', 'géologie', 'géomètre', 'géranium', 'germe', 'gestuel', 'geyser', 'gibier', 'gicler', 'girafe', 'givre', 'glace', 'glaive', 'glisser', 'globe', 'gloire', 'glorieux', 'golfeur', 'gomme', 'gonfler', 'gorge', 'gorille', 'goudron', 'gouffre', 'goulot', 'goupille', 'gourmand', 'goutte', 'graduel', 'graffiti', 'graine', 'grand', 'grappin', 'gratuit', 'gravir', 'grenat', 'griffure', 'griller', 'grimper', 'grogner', 'gronder', 'grotte', 'groupe', 'gruger', 'grutier', 'gruyère', 'guépard', 'guerrier', 'guide', 'guimauve', 'guitare', 'gustatif', 'gymnaste', 'gyrostat', 'habitude', 'hachoir', 'halte', 'hameau', 'hangar', 'hanneton', 'haricot', 'harmonie', 'harpon', 'hasard', 'hélium', 'hématome', 'herbe', 'hérisson', 'hermine', 'héron', 'hésiter', 'heureux', 'hiberner', 'hibou', 'hilarant', 'histoire', 'hiver', 'homard', 'hommage', 'homogène', 'honneur', 'honorer', 'honteux', 'horde', 'horizon', 'horloge', 'hormone', 'horrible', 'houleux', 'housse', 'hublot', 'huileux', 'humain', 'humble', 'humide', 'humour', 'hurler', 'hydromel', 'hygiène', 'hymne', 'hypnose', 'idylle', 'ignorer', 'iguane', 'illicite', 'illusion', 'image', 'imbiber', 'imiter', 'immense', 'immobile', 'immuable', 'impact', 'impérial', 'implorer', 'imposer', 'imprimer', 'imputer', 'incarner', 'incendie', 'incident', 'incliner', 'incolore', 'indexer', 'indice', 'inductif', 'inédit', 'ineptie', 'inexact', 'infini', 'infliger', 'informer', 'infusion', 'ingérer', 'inhaler', 'inhiber', 'injecter', 'injure', 'innocent', 'inoculer', 'inonder', 'inscrire', 'insecte', 'insigne', 'insolite', 'inspirer', 'instinct', 'insulter', 'intact', 'intense', 'intime', 'intrigue', 'intuitif', 'inutile', 'invasion', 'inventer', 'inviter', 'invoquer', 'ironique', 'irradier', 'irréel', 'irriter', 'isoler', 'ivoire', 'ivresse', 'jaguar', 'jaillir', 'jambe', 'janvier', 'jardin', 'jauger', 'jaune', 'javelot', 'jetable', 'jeton', 'jeudi', 'jeunesse', 'joindre', 'joncher', 'jongler', 'joueur', 'jouissif', 'journal', 'jovial', 'joyau', 'joyeux', 'jubiler', 'jugement', 'junior', 'jupon', 'juriste', 'justice', 'juteux', 'juvénile', 'kayak', 'kimono', 'kiosque', 'label', 'labial', 'labourer', 'lacérer', 'lactose', 'lagune', 'laine', 'laisser', 'laitier', 'lambeau', 'lamelle', 'lampe', 'lanceur', 'langage', 'lanterne', 'lapin', 'largeur', 'larme', 'laurier', 'lavabo', 'lavoir', 'lecture', 'légal', 'léger', 'légume', 'lessive', 'lettre', 'levier', 'lexique', 'lézard', 'liasse', 'libérer', 'libre', 'licence', 'licorne', 'liège', 'lièvre', 'ligature', 'ligoter', 'ligue', 'limer', 'limite', 'limonade', 'limpide', 'linéaire', 'lingot', 'lionceau', 'liquide', 'lisière', 'lister', 'lithium', 'litige', 'littoral', 'livreur', 'logique', 'lointain', 'loisir', 'lombric', 'loterie', 'louer', 'lourd', 'loutre', 'louve', 'loyal', 'lubie', 'lucide', 'lucratif', 'lueur', 'lugubre', 'luisant', 'lumière', 'lunaire', 'lundi', 'luron', 'lutter', 'luxueux', 'machine', 'magasin', 'magenta', 'magique', 'maigre', 'maillon', 'maintien', 'mairie', 'maison', 'majorer', 'malaxer', 'maléfice', 'malheur', 'malice', 'mallette', 'mammouth', 'mandater', 'maniable', 'manquant', 'manteau', 'manuel', 'marathon', 'marbre', 'marchand', 'mardi', 'maritime', 'marqueur', 'marron', 'marteler', 'mascotte', 'massif', 'matériel', 'matière', 'matraque', 'maudire', 'maussade', 'mauve', 'maximal', 'méchant', 'méconnu', 'médaille', 'médecin', 'méditer', 'méduse', 'meilleur', 'mélange', 'mélodie', 'membre', 'mémoire', 'menacer', 'mener', 'menhir', 'mensonge', 'mentor', 'mercredi', 'mérite', 'merle', 'messager', 'mesure', 'métal', 'météore', 'méthode', 'métier', 'meuble', 'miauler', 'microbe', 'miette', 'mignon', 'migrer', 'milieu', 'million', 'mimique', 'mince', 'minéral', 'minimal', 'minorer', 'minute', 'miracle', 'miroiter', 'missile', 'mixte', 'mobile', 'moderne', 'moelleux', 'mondial', 'moniteur', 'monnaie', 'monotone', 'monstre', 'montagne', 'monument', 'moqueur', 'morceau', 'morsure', 'mortier', 'moteur', 'motif', 'mouche', 'moufle', 'moulin', 'mousson', 'mouton', 'mouvant', 'multiple', 'munition', 'muraille', 'murène', 'murmure', 'muscle', 'muséum', 'musicien', 'mutation', 'muter', 'mutuel', 'myriade', 'myrtille', 'mystère', 'mythique', 'nageur', 'nappe', 'narquois', 'narrer', 'natation', 'nation', 'nature', 'naufrage', 'nautique', 'navire', 'nébuleux', 'nectar', 'néfaste', 'négation', 'négliger', 'négocier', 'neige', 'nerveux', 'nettoyer', 'neurone', 'neutron', 'neveu', 'niche', 'nickel', 'nitrate', 'niveau', 'noble', 'nocif', 'nocturne', 'noirceur', 'noisette', 'nomade', 'nombreux', 'nommer', 'normatif', 'notable', 'notifier', 'notoire', 'nourrir', 'nouveau', 'novateur', 'novembre', 'novice', 'nuage', 'nuancer', 'nuire', 'nuisible', 'numéro', 'nuptial', 'nuque', 'nutritif', 'obéir', 'objectif', 'obliger', 'obscur', 'observer', 'obstacle', 'obtenir', 'obturer', 'occasion', 'occuper', 'océan', 'octobre', 'octroyer', 'octupler', 'oculaire', 'odeur', 'odorant', 'offenser', 'officier', 'offrir', 'ogive', 'oiseau', 'oisillon', 'olfactif', 'olivier', 'ombrage', 'omettre', 'onctueux', 'onduler', 'onéreux', 'onirique', 'opale', 'opaque', 'opérer', 'opinion', 'opportun', 'opprimer', 'opter', 'optique', 'orageux', 'orange', 'orbite', 'ordonner', 'oreille', 'organe', 'orgueil', 'orifice', 'ornement', 'orque', 'ortie', 'osciller', 'osmose', 'ossature', 'otarie', 'ouragan', 'ourson', 'outil', 'outrager', 'ouvrage', 'ovation', 'oxyde', 'oxygène', 'ozone', 'paisible', 'palace', 'palmarès', 'palourde', 'palper', 'panache', 'panda', 'pangolin', 'paniquer', 'panneau', 'panorama', 'pantalon', 'papaye', 'papier', 'papoter', 'papyrus', 'paradoxe', 'parcelle', 'paresse', 'parfumer', 'parler', 'parole', 'parrain', 'parsemer', 'partager', 'parure', 'parvenir', 'passion', 'pastèque', 'paternel', 'patience', 'patron', 'pavillon', 'pavoiser', 'payer', 'paysage', 'peigne', 'peintre', 'pelage', 'pélican', 'pelle', 'pelouse', 'peluche', 'pendule', 'pénétrer', 'pénible', 'pensif', 'pénurie', 'pépite', 'péplum', 'perdrix', 'perforer', 'période', 'permuter', 'perplexe', 'persil', 'perte', 'peser', 'pétale', 'petit', 'pétrir', 'peuple', 'pharaon', 'phobie', 'phoque', 'photon', 'phrase', 'physique', 'piano', 'pictural', 'pièce', 'pierre', 'pieuvre', 'pilote', 'pinceau', 'pipette', 'piquer', 'pirogue', 'piscine', 'piston', 'pivoter', 'pixel', 'pizza', 'placard', 'plafond', 'plaisir', 'planer', 'plaque', 'plastron', 'plateau', 'pleurer', 'plexus', 'pliage', 'plomb', 'plonger', 'pluie', 'plumage', 'pochette', 'poésie', 'poète', 'pointe', 'poirier', 'poisson', 'poivre', 'polaire', 'policier', 'pollen', 'polygone', 'pommade', 'pompier', 'ponctuel', 'pondérer', 'poney', 'portique', 'position', 'posséder', 'posture', 'potager', 'poteau', 'potion', 'pouce', 'poulain', 'poumon', 'pourpre', 'poussin', 'pouvoir', 'prairie', 'pratique', 'précieux', 'prédire', 'préfixe', 'prélude', 'prénom', 'présence', 'prétexte', 'prévoir', 'primitif', 'prince', 'prison', 'priver', 'problème', 'procéder', 'prodige', 'profond', 'progrès', 'proie', 'projeter', 'prologue', 'promener', 'propre', 'prospère', 'protéger', 'prouesse', 'proverbe', 'prudence', 'pruneau', 'psychose', 'public', 'puceron', 'puiser', 'pulpe', 'pulsar', 'punaise', 'punitif', 'pupitre', 'purifier', 'puzzle', 'pyramide', 'quasar', 'querelle', 'question', 'quiétude', 'quitter', 'quotient', 'racine', 'raconter', 'radieux', 'ragondin', 'raideur', 'raisin', 'ralentir', 'rallonge', 'ramasser', 'rapide', 'rasage', 'ratisser', 'ravager', 'ravin', 'rayonner', 'réactif', 'réagir', 'réaliser', 'réanimer', 'recevoir', 'réciter', 'réclamer', 'récolter', 'recruter', 'reculer', 'recycler', 'rédiger', 'redouter', 'refaire', 'réflexe', 'réformer', 'refrain', 'refuge', 'régalien', 'région', 'réglage', 'régulier', 'réitérer', 'rejeter', 'rejouer', 'relatif', 'relever', 'relief', 'remarque', 'remède', 'remise', 'remonter', 'remplir', 'remuer', 'renard', 'renfort', 'renifler', 'renoncer', 'rentrer', 'renvoi', 'replier', 'reporter', 'reprise', 'reptile', 'requin', 'réserve', 'résineux', 'résoudre', 'respect', 'rester', 'résultat', 'rétablir', 'retenir', 'réticule', 'retomber', 'retracer', 'réunion', 'réussir', 'revanche', 'revivre', 'révolte', 'révulsif', 'richesse', 'rideau', 'rieur', 'rigide', 'rigoler', 'rincer', 'riposter', 'risible', 'risque', 'rituel', 'rival', 'rivière', 'rocheux', 'romance', 'rompre', 'ronce', 'rondin', 'roseau', 'rosier', 'rotatif', 'rotor', 'rotule', 'rouge', 'rouille', 'rouleau', 'routine', 'royaume', 'ruban', 'rubis', 'ruche', 'ruelle', 'rugueux', 'ruiner', 'ruisseau', 'ruser', 'rustique', 'rythme', 'sabler', 'saboter', 'sabre', 'sacoche', 'safari', 'sagesse', 'saisir', 'salade', 'salive', 'salon', 'saluer', 'samedi', 'sanction', 'sanglier', 'sarcasme', 'sardine', 'saturer', 'saugrenu', 'saumon', 'sauter', 'sauvage', 'savant', 'savonner', 'scalpel', 'scandale', 'scélérat', 'scénario', 'sceptre', 'schéma', 'science', 'scinder', 'score', 'scrutin', 'sculpter', 'séance', 'sécable', 'sécher', 'secouer', 'sécréter', 'sédatif', 'séduire', 'seigneur', 'séjour', 'sélectif', 'semaine', 'sembler', 'semence', 'séminal', 'sénateur', 'sensible', 'sentence', 'séparer', 'séquence', 'serein', 'sergent', 'sérieux', 'serrure', 'sérum', 'service', 'sésame', 'sévir', 'sevrage', 'sextuple', 'sidéral', 'siècle', 'siéger', 'siffler', 'sigle', 'signal', 'silence', 'silicium', 'simple', 'sincère', 'sinistre', 'siphon', 'sirop', 'sismique', 'situer', 'skier', 'social', 'socle', 'sodium', 'soigneux', 'soldat', 'soleil', 'solitude', 'soluble', 'sombre', 'sommeil', 'somnoler', 'sonde', 'songeur', 'sonnette', 'sonore', 'sorcier', 'sortir', 'sosie', 'sottise', 'soucieux', 'soudure', 'souffle', 'soulever', 'soupape', 'source', 'soutirer', 'souvenir', 'spacieux', 'spatial', 'spécial', 'sphère', 'spiral', 'stable', 'station', 'sternum', 'stimulus', 'stipuler', 'strict', 'studieux', 'stupeur', 'styliste', 'sublime', 'substrat', 'subtil', 'subvenir', 'succès', 'sucre', 'suffixe', 'suggérer', 'suiveur', 'sulfate', 'superbe', 'supplier', 'surface', 'suricate', 'surmener', 'surprise', 'sursaut', 'survie', 'suspect', 'syllabe', 'symbole', 'symétrie', 'synapse', 'syntaxe', 'système', 'tabac', 'tablier', 'tactile', 'tailler', 'talent', 'talisman', 'talonner', 'tambour', 'tamiser', 'tangible', 'tapis', 'taquiner', 'tarder', 'tarif', 'tartine', 'tasse', 'tatami', 'tatouage', 'taupe', 'taureau', 'taxer', 'témoin', 'temporel', 'tenaille', 'tendre', 'teneur', 'tenir', 'tension', 'terminer', 'terne', 'terrible', 'tétine', 'texte', 'thème', 'théorie', 'thérapie', 'thorax', 'tibia', 'tiède', 'timide', 'tirelire', 'tiroir', 'tissu', 'titane', 'titre', 'tituber', 'toboggan', 'tolérant', 'tomate', 'tonique', 'tonneau', 'toponyme', 'torche', 'tordre', 'tornade', 'torpille', 'torrent', 'torse', 'tortue', 'totem', 'toucher', 'tournage', 'tousser', 'toxine', 'traction', 'trafic', 'tragique', 'trahir', 'train', 'trancher', 'travail', 'trèfle', 'tremper', 'trésor', 'treuil', 'triage', 'tribunal', 'tricoter', 'trilogie', 'triomphe', 'tripler', 'triturer', 'trivial', 'trombone', 'tronc', 'tropical', 'troupeau', 'tuile', 'tulipe', 'tumulte', 'tunnel', 'turbine', 'tuteur', 'tutoyer', 'tuyau', 'tympan', 'typhon', 'typique', 'tyran', 'ubuesque', 'ultime', 'ultrason', 'unanime', 'unifier', 'union', 'unique', 'unitaire', 'univers', 'uranium', 'urbain', 'urticant', 'usage', 'usine', 'usuel', 'usure', 'utile', 'utopie', 'vacarme', 'vaccin', 'vagabond', 'vague', 'vaillant', 'vaincre', 'vaisseau', 'valable', 'valise', 'vallon', 'valve', 'vampire', 'vanille', 'vapeur', 'varier', 'vaseux', 'vassal', 'vaste', 'vecteur', 'vedette', 'végétal', 'véhicule', 'veinard', 'véloce', 'vendredi', 'vénérer', 'venger', 'venimeux', 'ventouse', 'verdure', 'vérin', 'vernir', 'verrou', 'verser', 'vertu', 'veston', 'vétéran', 'vétuste', 'vexant', 'vexer', 'viaduc', 'viande', 'victoire', 'vidange', 'vidéo', 'vignette', 'vigueur', 'vilain', 'village', 'vinaigre', 'violon', 'vipère', 'virement', 'virtuose', 'virus', 'visage', 'viseur', 'vision', 'visqueux', 'visuel', 'vital', 'vitesse', 'viticole', 'vitrine', 'vivace', 'vivipare', 'vocation', 'voguer', 'voile', 'voisin', 'voiture', 'volaille', 'volcan', 'voltiger', 'volume', 'vorace', 'vortex', 'voter', 'vouloir', 'voyage', 'voyelle', 'wagon', 'xénon', 'yacht', 'zèbre', 'zénith', 'zeste', 'zoologie']; + +export default french; \ No newline at end of file diff --git a/src/backend/wallet/lib/words/index.js b/src/backend/wallet/lib/words/index.js new file mode 100644 index 0000000..882901e --- /dev/null +++ b/src/backend/wallet/lib/words/index.js @@ -0,0 +1,15 @@ +import chinese from './chinese'; +import english from './english'; +import french from './french'; +import italian from './italian'; +import japanese from './japanese'; +import spanish from './spanish'; + +export default { + 'CHINESE': chinese, + 'ENGLISH': english, + 'FRENCH': french, + 'ITALIAN': italian, + 'JAPANESE': japanese, + 'SPANISH': spanish +}; diff --git a/src/backend/wallet/lib/words/italian.js b/src/backend/wallet/lib/words/italian.js new file mode 100644 index 0000000..68890b3 --- /dev/null +++ b/src/backend/wallet/lib/words/italian.js @@ -0,0 +1,5 @@ +'use strict'; + +var italian = ['abaco', 'abbaglio', 'abbinato', 'abete', 'abisso', 'abolire', 'abrasivo', 'abrogato', 'accadere', 'accenno', 'accusato', 'acetone', 'achille', 'acido', 'acqua', 'acre', 'acrilico', 'acrobata', 'acuto', 'adagio', 'addebito', 'addome', 'adeguato', 'aderire', 'adipe', 'adottare', 'adulare', 'affabile', 'affetto', 'affisso', 'affranto', 'aforisma', 'afoso', 'africano', 'agave', 'agente', 'agevole', 'aggancio', 'agire', 'agitare', 'agonismo', 'agricolo', 'agrumeto', 'aguzzo', 'alabarda', 'alato', 'albatro', 'alberato', 'albo', 'albume', 'alce', 'alcolico', 'alettone', 'alfa', 'algebra', 'aliante', 'alibi', 'alimento', 'allagato', 'allegro', 'allievo', 'allodola', 'allusivo', 'almeno', 'alogeno', 'alpaca', 'alpestre', 'altalena', 'alterno', 'alticcio', 'altrove', 'alunno', 'alveolo', 'alzare', 'amalgama', 'amanita', 'amarena', 'ambito', 'ambrato', 'ameba', 'america', 'ametista', 'amico', 'ammasso', 'ammenda', 'ammirare', 'ammonito', 'amore', 'ampio', 'ampliare', 'amuleto', 'anacardo', 'anagrafe', 'analista', 'anarchia', 'anatra', 'anca', 'ancella', 'ancora', 'andare', 'andrea', 'anello', 'angelo', 'angolare', 'angusto', 'anima', 'annegare', 'annidato', 'anno', 'annuncio', 'anonimo', 'anticipo', 'anzi', 'apatico', 'apertura', 'apode', 'apparire', 'appetito', 'appoggio', 'approdo', 'appunto', 'aprile', 'arabica', 'arachide', 'aragosta', 'araldica', 'arancio', 'aratura', 'arazzo', 'arbitro', 'archivio', 'ardito', 'arenile', 'argento', 'argine', 'arguto', 'aria', 'armonia', 'arnese', 'arredato', 'arringa', 'arrosto', 'arsenico', 'arso', 'artefice', 'arzillo', 'asciutto', 'ascolto', 'asepsi', 'asettico', 'asfalto', 'asino', 'asola', 'aspirato', 'aspro', 'assaggio', 'asse', 'assoluto', 'assurdo', 'asta', 'astenuto', 'astice', 'astratto', 'atavico', 'ateismo', 'atomico', 'atono', 'attesa', 'attivare', 'attorno', 'attrito', 'attuale', 'ausilio', 'austria', 'autista', 'autonomo', 'autunno', 'avanzato', 'avere', 'avvenire', 'avviso', 'avvolgere', 'azione', 'azoto', 'azzimo', 'azzurro', 'babele', 'baccano', 'bacino', 'baco', 'badessa', 'badilata', 'bagnato', 'baita', 'balcone', 'baldo', 'balena', 'ballata', 'balzano', 'bambino', 'bandire', 'baraonda', 'barbaro', 'barca', 'baritono', 'barlume', 'barocco', 'basilico', 'basso', 'batosta', 'battuto', 'baule', 'bava', 'bavosa', 'becco', 'beffa', 'belgio', 'belva', 'benda', 'benevole', 'benigno', 'benzina', 'bere', 'berlina', 'beta', 'bibita', 'bici', 'bidone', 'bifido', 'biga', 'bilancia', 'bimbo', 'binocolo', 'biologo', 'bipede', 'bipolare', 'birbante', 'birra', 'biscotto', 'bisesto', 'bisnonno', 'bisonte', 'bisturi', 'bizzarro', 'blando', 'blatta', 'bollito', 'bonifico', 'bordo', 'bosco', 'botanico', 'bottino', 'bozzolo', 'braccio', 'bradipo', 'brama', 'branca', 'bravura', 'bretella', 'brevetto', 'brezza', 'briglia', 'brillante', 'brindare', 'broccolo', 'brodo', 'bronzina', 'brullo', 'bruno', 'bubbone', 'buca', 'budino', 'buffone', 'buio', 'bulbo', 'buono', 'burlone', 'burrasca', 'bussola', 'busta', 'cadetto', 'caduco', 'calamaro', 'calcolo', 'calesse', 'calibro', 'calmo', 'caloria', 'cambusa', 'camerata', 'camicia', 'cammino', 'camola', 'campale', 'canapa', 'candela', 'cane', 'canino', 'canotto', 'cantina', 'capace', 'capello', 'capitolo', 'capogiro', 'cappero', 'capra', 'capsula', 'carapace', 'carcassa', 'cardo', 'carisma', 'carovana', 'carretto', 'cartolina', 'casaccio', 'cascata', 'caserma', 'caso', 'cassone', 'castello', 'casuale', 'catasta', 'catena', 'catrame', 'cauto', 'cavillo', 'cedibile', 'cedrata', 'cefalo', 'celebre', 'cellulare', 'cena', 'cenone', 'centesimo', 'ceramica', 'cercare', 'certo', 'cerume', 'cervello', 'cesoia', 'cespo', 'ceto', 'chela', 'chiaro', 'chicca', 'chiedere', 'chimera', 'china', 'chirurgo', 'chitarra', 'ciao', 'ciclismo', 'cifrare', 'cigno', 'cilindro', 'ciottolo', 'circa', 'cirrosi', 'citrico', 'cittadino', 'ciuffo', 'civetta', 'civile', 'classico', 'clinica', 'cloro', 'cocco', 'codardo', 'codice', 'coerente', 'cognome', 'collare', 'colmato', 'colore', 'colposo', 'coltivato', 'colza', 'coma', 'cometa', 'commando', 'comodo', 'computer', 'comune', 'conciso', 'condurre', 'conferma', 'congelare', 'coniuge', 'connesso', 'conoscere', 'consumo', 'continuo', 'convegno', 'coperto', 'copione', 'coppia', 'copricapo', 'corazza', 'cordata', 'coricato', 'cornice', 'corolla', 'corpo', 'corredo', 'corsia', 'cortese', 'cosmico', 'costante', 'cottura', 'covato', 'cratere', 'cravatta', 'creato', 'credere', 'cremoso', 'crescita', 'creta', 'criceto', 'crinale', 'crisi', 'critico', 'croce', 'cronaca', 'crostata', 'cruciale', 'crusca', 'cucire', 'cuculo', 'cugino', 'cullato', 'cupola', 'curatore', 'cursore', 'curvo', 'cuscino', 'custode', 'dado', 'daino', 'dalmata', 'damerino', 'daniela', 'dannoso', 'danzare', 'datato', 'davanti', 'davvero', 'debutto', 'decennio', 'deciso', 'declino', 'decollo', 'decreto', 'dedicato', 'definito', 'deforme', 'degno', 'delegare', 'delfino', 'delirio', 'delta', 'demenza', 'denotato', 'dentro', 'deposito', 'derapata', 'derivare', 'deroga', 'descritto', 'deserto', 'desiderio', 'desumere', 'detersivo', 'devoto', 'diametro', 'dicembre', 'diedro', 'difeso', 'diffuso', 'digerire', 'digitale', 'diluvio', 'dinamico', 'dinnanzi', 'dipinto', 'diploma', 'dipolo', 'diradare', 'dire', 'dirotto', 'dirupo', 'disagio', 'discreto', 'disfare', 'disgelo', 'disposto', 'distanza', 'disumano', 'dito', 'divano', 'divelto', 'dividere', 'divorato', 'doblone', 'docente', 'doganale', 'dogma', 'dolce', 'domato', 'domenica', 'dominare', 'dondolo', 'dono', 'dormire', 'dote', 'dottore', 'dovuto', 'dozzina', 'drago', 'druido', 'dubbio', 'dubitare', 'ducale', 'duna', 'duomo', 'duplice', 'duraturo', 'ebano', 'eccesso', 'ecco', 'eclissi', 'economia', 'edera', 'edicola', 'edile', 'editoria', 'educare', 'egemonia', 'egli', 'egoismo', 'egregio', 'elaborato', 'elargire', 'elegante', 'elencato', 'eletto', 'elevare', 'elfico', 'elica', 'elmo', 'elsa', 'eluso', 'emanato', 'emblema', 'emesso', 'emiro', 'emotivo', 'emozione', 'empirico', 'emulo', 'endemico', 'enduro', 'energia', 'enfasi', 'enoteca', 'entrare', 'enzima', 'epatite', 'epilogo', 'episodio', 'epocale', 'eppure', 'equatore', 'erario', 'erba', 'erboso', 'erede', 'eremita', 'erigere', 'ermetico', 'eroe', 'erosivo', 'errante', 'esagono', 'esame', 'esanime', 'esaudire', 'esca', 'esempio', 'esercito', 'esibito', 'esigente', 'esistere', 'esito', 'esofago', 'esortato', 'esoso', 'espanso', 'espresso', 'essenza', 'esso', 'esteso', 'estimare', 'estonia', 'estroso', 'esultare', 'etilico', 'etnico', 'etrusco', 'etto', 'euclideo', 'europa', 'evaso', 'evidenza', 'evitato', 'evoluto', 'evviva', 'fabbrica', 'faccenda', 'fachiro', 'falco', 'famiglia', 'fanale', 'fanfara', 'fango', 'fantasma', 'fare', 'farfalla', 'farinoso', 'farmaco', 'fascia', 'fastoso', 'fasullo', 'faticare', 'fato', 'favoloso', 'febbre', 'fecola', 'fede', 'fegato', 'felpa', 'feltro', 'femmina', 'fendere', 'fenomeno', 'fermento', 'ferro', 'fertile', 'fessura', 'festivo', 'fetta', 'feudo', 'fiaba', 'fiducia', 'fifa', 'figurato', 'filo', 'finanza', 'finestra', 'finire', 'fiore', 'fiscale', 'fisico', 'fiume', 'flacone', 'flamenco', 'flebo', 'flemma', 'florido', 'fluente', 'fluoro', 'fobico', 'focaccia', 'focoso', 'foderato', 'foglio', 'folata', 'folclore', 'folgore', 'fondente', 'fonetico', 'fonia', 'fontana', 'forbito', 'forchetta', 'foresta', 'formica', 'fornaio', 'foro', 'fortezza', 'forzare', 'fosfato', 'fosso', 'fracasso', 'frana', 'frassino', 'fratello', 'freccetta', 'frenata', 'fresco', 'frigo', 'frollino', 'fronde', 'frugale', 'frutta', 'fucilata', 'fucsia', 'fuggente', 'fulmine', 'fulvo', 'fumante', 'fumetto', 'fumoso', 'fune', 'funzione', 'fuoco', 'furbo', 'furgone', 'furore', 'fuso', 'futile', 'gabbiano', 'gaffe', 'galateo', 'gallina', 'galoppo', 'gambero', 'gamma', 'garanzia', 'garbo', 'garofano', 'garzone', 'gasdotto', 'gasolio', 'gastrico', 'gatto', 'gaudio', 'gazebo', 'gazzella', 'geco', 'gelatina', 'gelso', 'gemello', 'gemmato', 'gene', 'genitore', 'gennaio', 'genotipo', 'gergo', 'ghepardo', 'ghiaccio', 'ghisa', 'giallo', 'gilda', 'ginepro', 'giocare', 'gioiello', 'giorno', 'giove', 'girato', 'girone', 'gittata', 'giudizio', 'giurato', 'giusto', 'globulo', 'glutine', 'gnomo', 'gobba', 'golf', 'gomito', 'gommone', 'gonfio', 'gonna', 'governo', 'gracile', 'grado', 'grafico', 'grammo', 'grande', 'grattare', 'gravoso', 'grazia', 'greca', 'gregge', 'grifone', 'grigio', 'grinza', 'grotta', 'gruppo', 'guadagno', 'guaio', 'guanto', 'guardare', 'gufo', 'guidare', 'ibernato', 'icona', 'identico', 'idillio', 'idolo', 'idra', 'idrico', 'idrogeno', 'igiene', 'ignaro', 'ignorato', 'ilare', 'illeso', 'illogico', 'illudere', 'imballo', 'imbevuto', 'imbocco', 'imbuto', 'immane', 'immerso', 'immolato', 'impacco', 'impeto', 'impiego', 'importo', 'impronta', 'inalare', 'inarcare', 'inattivo', 'incanto', 'incendio', 'inchino', 'incisivo', 'incluso', 'incontro', 'incrocio', 'incubo', 'indagine', 'india', 'indole', 'inedito', 'infatti', 'infilare', 'inflitto', 'ingaggio', 'ingegno', 'inglese', 'ingordo', 'ingrosso', 'innesco', 'inodore', 'inoltrare', 'inondato', 'insano', 'insetto', 'insieme', 'insonnia', 'insulina', 'intasato', 'intero', 'intonaco', 'intuito', 'inumidire', 'invalido', 'invece', 'invito', 'iperbole', 'ipnotico', 'ipotesi', 'ippica', 'iride', 'irlanda', 'ironico', 'irrigato', 'irrorare', 'isolato', 'isotopo', 'isterico', 'istituto', 'istrice', 'italia', 'iterare', 'labbro', 'labirinto', 'lacca', 'lacerato', 'lacrima', 'lacuna', 'laddove', 'lago', 'lampo', 'lancetta', 'lanterna', 'lardoso', 'larga', 'laringe', 'lastra', 'latenza', 'latino', 'lattuga', 'lavagna', 'lavoro', 'legale', 'leggero', 'lembo', 'lentezza', 'lenza', 'leone', 'lepre', 'lesivo', 'lessato', 'lesto', 'letterale', 'leva', 'levigato', 'libero', 'lido', 'lievito', 'lilla', 'limatura', 'limitare', 'limpido', 'lineare', 'lingua', 'liquido', 'lira', 'lirica', 'lisca', 'lite', 'litigio', 'livrea', 'locanda', 'lode', 'logica', 'lombare', 'londra', 'longevo', 'loquace', 'lorenzo', 'loto', 'lotteria', 'luce', 'lucidato', 'lumaca', 'luminoso', 'lungo', 'lupo', 'luppolo', 'lusinga', 'lusso', 'lutto', 'macabro', 'macchina', 'macero', 'macinato', 'madama', 'magico', 'maglia', 'magnete', 'magro', 'maiolica', 'malafede', 'malgrado', 'malinteso', 'malsano', 'malto', 'malumore', 'mana', 'mancia', 'mandorla', 'mangiare', 'manifesto', 'mannaro', 'manovra', 'mansarda', 'mantide', 'manubrio', 'mappa', 'maratona', 'marcire', 'maretta', 'marmo', 'marsupio', 'maschera', 'massaia', 'mastino', 'materasso', 'matricola', 'mattone', 'maturo', 'mazurca', 'meandro', 'meccanico', 'mecenate', 'medesimo', 'meditare', 'mega', 'melassa', 'melis', 'melodia', 'meninge', 'meno', 'mensola', 'mercurio', 'merenda', 'merlo', 'meschino', 'mese', 'messere', 'mestolo', 'metallo', 'metodo', 'mettere', 'miagolare', 'mica', 'micelio', 'michele', 'microbo', 'midollo', 'miele', 'migliore', 'milano', 'milite', 'mimosa', 'minerale', 'mini', 'minore', 'mirino', 'mirtillo', 'miscela', 'missiva', 'misto', 'misurare', 'mitezza', 'mitigare', 'mitra', 'mittente', 'mnemonico', 'modello', 'modifica', 'modulo', 'mogano', 'mogio', 'mole', 'molosso', 'monastero', 'monco', 'mondina', 'monetario', 'monile', 'monotono', 'monsone', 'montato', 'monviso', 'mora', 'mordere', 'morsicato', 'mostro', 'motivato', 'motosega', 'motto', 'movenza', 'movimento', 'mozzo', 'mucca', 'mucosa', 'muffa', 'mughetto', 'mugnaio', 'mulatto', 'mulinello', 'multiplo', 'mummia', 'munto', 'muovere', 'murale', 'musa', 'muscolo', 'musica', 'mutevole', 'muto', 'nababbo', 'nafta', 'nanometro', 'narciso', 'narice', 'narrato', 'nascere', 'nastrare', 'naturale', 'nautica', 'naviglio', 'nebulosa', 'necrosi', 'negativo', 'negozio', 'nemmeno', 'neofita', 'neretto', 'nervo', 'nessuno', 'nettuno', 'neutrale', 'neve', 'nevrotico', 'nicchia', 'ninfa', 'nitido', 'nobile', 'nocivo', 'nodo', 'nome', 'nomina', 'nordico', 'normale', 'norvegese', 'nostrano', 'notare', 'notizia', 'notturno', 'novella', 'nucleo', 'nulla', 'numero', 'nuovo', 'nutrire', 'nuvola', 'nuziale', 'oasi', 'obbedire', 'obbligo', 'obelisco', 'oblio', 'obolo', 'obsoleto', 'occasione', 'occhio', 'occidente', 'occorrere', 'occultare', 'ocra', 'oculato', 'odierno', 'odorare', 'offerta', 'offrire', 'offuscato', 'oggetto', 'oggi', 'ognuno', 'olandese', 'olfatto', 'oliato', 'oliva', 'ologramma', 'oltre', 'omaggio', 'ombelico', 'ombra', 'omega', 'omissione', 'ondoso', 'onere', 'onice', 'onnivoro', 'onorevole', 'onta', 'operato', 'opinione', 'opposto', 'oracolo', 'orafo', 'ordine', 'orecchino', 'orefice', 'orfano', 'organico', 'origine', 'orizzonte', 'orma', 'ormeggio', 'ornativo', 'orologio', 'orrendo', 'orribile', 'ortensia', 'ortica', 'orzata', 'orzo', 'osare', 'oscurare', 'osmosi', 'ospedale', 'ospite', 'ossa', 'ossidare', 'ostacolo', 'oste', 'otite', 'otre', 'ottagono', 'ottimo', 'ottobre', 'ovale', 'ovest', 'ovino', 'oviparo', 'ovocito', 'ovunque', 'ovviare', 'ozio', 'pacchetto', 'pace', 'pacifico', 'padella', 'padrone', 'paese', 'paga', 'pagina', 'palazzina', 'palesare', 'pallido', 'palo', 'palude', 'pandoro', 'pannello', 'paolo', 'paonazzo', 'paprica', 'parabola', 'parcella', 'parere', 'pargolo', 'pari', 'parlato', 'parola', 'partire', 'parvenza', 'parziale', 'passivo', 'pasticca', 'patacca', 'patologia', 'pattume', 'pavone', 'peccato', 'pedalare', 'pedonale', 'peggio', 'peloso', 'penare', 'pendice', 'penisola', 'pennuto', 'penombra', 'pensare', 'pentola', 'pepe', 'pepita', 'perbene', 'percorso', 'perdonato', 'perforare', 'pergamena', 'periodo', 'permesso', 'perno', 'perplesso', 'persuaso', 'pertugio', 'pervaso', 'pesatore', 'pesista', 'peso', 'pestifero', 'petalo', 'pettine', 'petulante', 'pezzo', 'piacere', 'pianta', 'piattino', 'piccino', 'picozza', 'piega', 'pietra', 'piffero', 'pigiama', 'pigolio', 'pigro', 'pila', 'pilifero', 'pillola', 'pilota', 'pimpante', 'pineta', 'pinna', 'pinolo', 'pioggia', 'piombo', 'piramide', 'piretico', 'pirite', 'pirolisi', 'pitone', 'pizzico', 'placebo', 'planare', 'plasma', 'platano', 'plenario', 'pochezza', 'poderoso', 'podismo', 'poesia', 'poggiare', 'polenta', 'poligono', 'pollice', 'polmonite', 'polpetta', 'polso', 'poltrona', 'polvere', 'pomice', 'pomodoro', 'ponte', 'popoloso', 'porfido', 'poroso', 'porpora', 'porre', 'portata', 'posa', 'positivo', 'possesso', 'postulato', 'potassio', 'potere', 'pranzo', 'prassi', 'pratica', 'precluso', 'predica', 'prefisso', 'pregiato', 'prelievo', 'premere', 'prenotare', 'preparato', 'presenza', 'pretesto', 'prevalso', 'prima', 'principe', 'privato', 'problema', 'procura', 'produrre', 'profumo', 'progetto', 'prolunga', 'promessa', 'pronome', 'proposta', 'proroga', 'proteso', 'prova', 'prudente', 'prugna', 'prurito', 'psiche', 'pubblico', 'pudica', 'pugilato', 'pugno', 'pulce', 'pulito', 'pulsante', 'puntare', 'pupazzo', 'pupilla', 'puro', 'quadro', 'qualcosa', 'quasi', 'querela', 'quota', 'raccolto', 'raddoppio', 'radicale', 'radunato', 'raffica', 'ragazzo', 'ragione', 'ragno', 'ramarro', 'ramingo', 'ramo', 'randagio', 'rantolare', 'rapato', 'rapina', 'rappreso', 'rasatura', 'raschiato', 'rasente', 'rassegna', 'rastrello', 'rata', 'ravveduto', 'reale', 'recepire', 'recinto', 'recluta', 'recondito', 'recupero', 'reddito', 'redimere', 'regalato', 'registro', 'regola', 'regresso', 'relazione', 'remare', 'remoto', 'renna', 'replica', 'reprimere', 'reputare', 'resa', 'residente', 'responso', 'restauro', 'rete', 'retina', 'retorica', 'rettifica', 'revocato', 'riassunto', 'ribadire', 'ribelle', 'ribrezzo', 'ricarica', 'ricco', 'ricevere', 'riciclato', 'ricordo', 'ricreduto', 'ridicolo', 'ridurre', 'rifasare', 'riflesso', 'riforma', 'rifugio', 'rigare', 'rigettato', 'righello', 'rilassato', 'rilevato', 'rimanere', 'rimbalzo', 'rimedio', 'rimorchio', 'rinascita', 'rincaro', 'rinforzo', 'rinnovo', 'rinomato', 'rinsavito', 'rintocco', 'rinuncia', 'rinvenire', 'riparato', 'ripetuto', 'ripieno', 'riportare', 'ripresa', 'ripulire', 'risata', 'rischio', 'riserva', 'risibile', 'riso', 'rispetto', 'ristoro', 'risultato', 'risvolto', 'ritardo', 'ritegno', 'ritmico', 'ritrovo', 'riunione', 'riva', 'riverso', 'rivincita', 'rivolto', 'rizoma', 'roba', 'robotico', 'robusto', 'roccia', 'roco', 'rodaggio', 'rodere', 'roditore', 'rogito', 'rollio', 'romantico', 'rompere', 'ronzio', 'rosolare', 'rospo', 'rotante', 'rotondo', 'rotula', 'rovescio', 'rubizzo', 'rubrica', 'ruga', 'rullino', 'rumine', 'rumoroso', 'ruolo', 'rupe', 'russare', 'rustico', 'sabato', 'sabbiare', 'sabotato', 'sagoma', 'salasso', 'saldatura', 'salgemma', 'salivare', 'salmone', 'salone', 'saltare', 'saluto', 'salvo', 'sapere', 'sapido', 'saporito', 'saraceno', 'sarcasmo', 'sarto', 'sassoso', 'satellite', 'satira', 'satollo', 'saturno', 'savana', 'savio', 'saziato', 'sbadiglio', 'sbalzo', 'sbancato', 'sbarra', 'sbattere', 'sbavare', 'sbendare', 'sbirciare', 'sbloccato', 'sbocciato', 'sbrinare', 'sbruffone', 'sbuffare', 'scabroso', 'scadenza', 'scala', 'scambiare', 'scandalo', 'scapola', 'scarso', 'scatenare', 'scavato', 'scelto', 'scenico', 'scettro', 'scheda', 'schiena', 'sciarpa', 'scienza', 'scindere', 'scippo', 'sciroppo', 'scivolo', 'sclerare', 'scodella', 'scolpito', 'scomparto', 'sconforto', 'scoprire', 'scorta', 'scossone', 'scozzese', 'scriba', 'scrollare', 'scrutinio', 'scuderia', 'scultore', 'scuola', 'scuro', 'scusare', 'sdebitare', 'sdoganare', 'seccatura', 'secondo', 'sedano', 'seggiola', 'segnalato', 'segregato', 'seguito', 'selciato', 'selettivo', 'sella', 'selvaggio', 'semaforo', 'sembrare', 'seme', 'seminato', 'sempre', 'senso', 'sentire', 'sepolto', 'sequenza', 'serata', 'serbato', 'sereno', 'serio', 'serpente', 'serraglio', 'servire', 'sestina', 'setola', 'settimana', 'sfacelo', 'sfaldare', 'sfamato', 'sfarzoso', 'sfaticato', 'sfera', 'sfida', 'sfilato', 'sfinge', 'sfocato', 'sfoderare', 'sfogo', 'sfoltire', 'sforzato', 'sfratto', 'sfruttato', 'sfuggito', 'sfumare', 'sfuso', 'sgabello', 'sgarbato', 'sgonfiare', 'sgorbio', 'sgrassato', 'sguardo', 'sibilo', 'siccome', 'sierra', 'sigla', 'signore', 'silenzio', 'sillaba', 'simbolo', 'simpatico', 'simulato', 'sinfonia', 'singolo', 'sinistro', 'sino', 'sintesi', 'sinusoide', 'sipario', 'sisma', 'sistole', 'situato', 'slitta', 'slogatura', 'sloveno', 'smarrito', 'smemorato', 'smentito', 'smeraldo', 'smilzo', 'smontare', 'smottato', 'smussato', 'snellire', 'snervato', 'snodo', 'sobbalzo', 'sobrio', 'soccorso', 'sociale', 'sodale', 'soffitto', 'sogno', 'soldato', 'solenne', 'solido', 'sollazzo', 'solo', 'solubile', 'solvente', 'somatico', 'somma', 'sonda', 'sonetto', 'sonnifero', 'sopire', 'soppeso', 'sopra', 'sorgere', 'sorpasso', 'sorriso', 'sorso', 'sorteggio', 'sorvolato', 'sospiro', 'sosta', 'sottile', 'spada', 'spalla', 'spargere', 'spatola', 'spavento', 'spazzola', 'specie', 'spedire', 'spegnere', 'spelatura', 'speranza', 'spessore', 'spettrale', 'spezzato', 'spia', 'spigoloso', 'spillato', 'spinoso', 'spirale', 'splendido', 'sportivo', 'sposo', 'spranga', 'sprecare', 'spronato', 'spruzzo', 'spuntino', 'squillo', 'sradicare', 'srotolato', 'stabile', 'stacco', 'staffa', 'stagnare', 'stampato', 'stantio', 'starnuto', 'stasera', 'statuto', 'stelo', 'steppa', 'sterzo', 'stiletto', 'stima', 'stirpe', 'stivale', 'stizzoso', 'stonato', 'storico', 'strappo', 'stregato', 'stridulo', 'strozzare', 'strutto', 'stuccare', 'stufo', 'stupendo', 'subentro', 'succoso', 'sudore', 'suggerito', 'sugo', 'sultano', 'suonare', 'superbo', 'supporto', 'surgelato', 'surrogato', 'sussurro', 'sutura', 'svagare', 'svedese', 'sveglio', 'svelare', 'svenuto', 'svezia', 'sviluppo', 'svista', 'svizzera', 'svolta', 'svuotare', 'tabacco', 'tabulato', 'tacciare', 'taciturno', 'tale', 'talismano', 'tampone', 'tannino', 'tara', 'tardivo', 'targato', 'tariffa', 'tarpare', 'tartaruga', 'tasto', 'tattico', 'taverna', 'tavolata', 'tazza', 'teca', 'tecnico', 'telefono', 'temerario', 'tempo', 'temuto', 'tendone', 'tenero', 'tensione', 'tentacolo', 'teorema', 'terme', 'terrazzo', 'terzetto', 'tesi', 'tesserato', 'testato', 'tetro', 'tettoia', 'tifare', 'tigella', 'timbro', 'tinto', 'tipico', 'tipografo', 'tiraggio', 'tiro', 'titanio', 'titolo', 'titubante', 'tizio', 'tizzone', 'toccare', 'tollerare', 'tolto', 'tombola', 'tomo', 'tonfo', 'tonsilla', 'topazio', 'topologia', 'toppa', 'torba', 'tornare', 'torrone', 'tortora', 'toscano', 'tossire', 'tostatura', 'totano', 'trabocco', 'trachea', 'trafila', 'tragedia', 'tralcio', 'tramonto', 'transito', 'trapano', 'trarre', 'trasloco', 'trattato', 'trave', 'treccia', 'tremolio', 'trespolo', 'tributo', 'tricheco', 'trifoglio', 'trillo', 'trincea', 'trio', 'tristezza', 'triturato', 'trivella', 'tromba', 'trono', 'troppo', 'trottola', 'trovare', 'truccato', 'tubatura', 'tuffato', 'tulipano', 'tumulto', 'tunisia', 'turbare', 'turchino', 'tuta', 'tutela', 'ubicato', 'uccello', 'uccisore', 'udire', 'uditivo', 'uffa', 'ufficio', 'uguale', 'ulisse', 'ultimato', 'umano', 'umile', 'umorismo', 'uncinetto', 'ungere', 'ungherese', 'unicorno', 'unificato', 'unisono', 'unitario', 'unte', 'uovo', 'upupa', 'uragano', 'urgenza', 'urlo', 'usanza', 'usato', 'uscito', 'usignolo', 'usuraio', 'utensile', 'utilizzo', 'utopia', 'vacante', 'vaccinato', 'vagabondo', 'vagliato', 'valanga', 'valgo', 'valico', 'valletta', 'valoroso', 'valutare', 'valvola', 'vampata', 'vangare', 'vanitoso', 'vano', 'vantaggio', 'vanvera', 'vapore', 'varano', 'varcato', 'variante', 'vasca', 'vedetta', 'vedova', 'veduto', 'vegetale', 'veicolo', 'velcro', 'velina', 'velluto', 'veloce', 'venato', 'vendemmia', 'vento', 'verace', 'verbale', 'vergogna', 'verifica', 'vero', 'verruca', 'verticale', 'vescica', 'vessillo', 'vestale', 'veterano', 'vetrina', 'vetusto', 'viandante', 'vibrante', 'vicenda', 'vichingo', 'vicinanza', 'vidimare', 'vigilia', 'vigneto', 'vigore', 'vile', 'villano', 'vimini', 'vincitore', 'viola', 'vipera', 'virgola', 'virologo', 'virulento', 'viscoso', 'visione', 'vispo', 'vissuto', 'visura', 'vita', 'vitello', 'vittima', 'vivanda', 'vivido', 'viziare', 'voce', 'voga', 'volatile', 'volere', 'volpe', 'voragine', 'vulcano', 'zampogna', 'zanna', 'zappato', 'zattera', 'zavorra', 'zefiro', 'zelante', 'zelo', 'zenzero', 'zerbino', 'zibetto', 'zinco', 'zircone', 'zitto', 'zolla', 'zotico', 'zucchero', 'zufolo', 'zulu', 'zuppa']; + +export default italian; diff --git a/src/backend/wallet/lib/words/japanese.js b/src/backend/wallet/lib/words/japanese.js new file mode 100644 index 0000000..2cea91e --- /dev/null +++ b/src/backend/wallet/lib/words/japanese.js @@ -0,0 +1,5 @@ +'use strict'; + +var japanese = ['あいこくしん', 'あいさつ', 'あいだ', 'あおぞら', 'あかちゃん', 'あきる', 'あけがた', 'あける', 'あこがれる', 'あさい', 'あさひ', 'あしあと', 'あじわう', 'あずかる', 'あずき', 'あそぶ', 'あたえる', 'あたためる', 'あたりまえ', 'あたる', 'あつい', 'あつかう', 'あっしゅく', 'あつまり', 'あつめる', 'あてな', 'あてはまる', 'あひる', 'あぶら', 'あぶる', 'あふれる', 'あまい', 'あまど', 'あまやかす', 'あまり', 'あみもの', 'あめりか', 'あやまる', 'あゆむ', 'あらいぐま', 'あらし', 'あらすじ', 'あらためる', 'あらゆる', 'あらわす', 'ありがとう', 'あわせる', 'あわてる', 'あんい', 'あんがい', 'あんこ', 'あんぜん', 'あんてい', 'あんない', 'あんまり', 'いいだす', 'いおん', 'いがい', 'いがく', 'いきおい', 'いきなり', 'いきもの', 'いきる', 'いくじ', 'いくぶん', 'いけばな', 'いけん', 'いこう', 'いこく', 'いこつ', 'いさましい', 'いさん', 'いしき', 'いじゅう', 'いじょう', 'いじわる', 'いずみ', 'いずれ', 'いせい', 'いせえび', 'いせかい', 'いせき', 'いぜん', 'いそうろう', 'いそがしい', 'いだい', 'いだく', 'いたずら', 'いたみ', 'いたりあ', 'いちおう', 'いちじ', 'いちど', 'いちば', 'いちぶ', 'いちりゅう', 'いつか', 'いっしゅん', 'いっせい', 'いっそう', 'いったん', 'いっち', 'いってい', 'いっぽう', 'いてざ', 'いてん', 'いどう', 'いとこ', 'いない', 'いなか', 'いねむり', 'いのち', 'いのる', 'いはつ', 'いばる', 'いはん', 'いびき', 'いひん', 'いふく', 'いへん', 'いほう', 'いみん', 'いもうと', 'いもたれ', 'いもり', 'いやがる', 'いやす', 'いよかん', 'いよく', 'いらい', 'いらすと', 'いりぐち', 'いりょう', 'いれい', 'いれもの', 'いれる', 'いろえんぴつ', 'いわい', 'いわう', 'いわかん', 'いわば', 'いわゆる', 'いんげんまめ', 'いんさつ', 'いんしょう', 'いんよう', 'うえき', 'うえる', 'うおざ', 'うがい', 'うかぶ', 'うかべる', 'うきわ', 'うくらいな', 'うくれれ', 'うけたまわる', 'うけつけ', 'うけとる', 'うけもつ', 'うける', 'うごかす', 'うごく', 'うこん', 'うさぎ', 'うしなう', 'うしろがみ', 'うすい', 'うすぎ', 'うすぐらい', 'うすめる', 'うせつ', 'うちあわせ', 'うちがわ', 'うちき', 'うちゅう', 'うっかり', 'うつくしい', 'うったえる', 'うつる', 'うどん', 'うなぎ', 'うなじ', 'うなずく', 'うなる', 'うねる', 'うのう', 'うぶげ', 'うぶごえ', 'うまれる', 'うめる', 'うもう', 'うやまう', 'うよく', 'うらがえす', 'うらぐち', 'うらない', 'うりあげ', 'うりきれ', 'うるさい', 'うれしい', 'うれゆき', 'うれる', 'うろこ', 'うわき', 'うわさ', 'うんこう', 'うんちん', 'うんてん', 'うんどう', 'えいえん', 'えいが', 'えいきょう', 'えいご', 'えいせい', 'えいぶん', 'えいよう', 'えいわ', 'えおり', 'えがお', 'えがく', 'えきたい', 'えくせる', 'えしゃく', 'えすて', 'えつらん', 'えのぐ', 'えほうまき', 'えほん', 'えまき', 'えもじ', 'えもの', 'えらい', 'えらぶ', 'えりあ', 'えんえん', 'えんかい', 'えんぎ', 'えんげき', 'えんしゅう', 'えんぜつ', 'えんそく', 'えんちょう', 'えんとつ', 'おいかける', 'おいこす', 'おいしい', 'おいつく', 'おうえん', 'おうさま', 'おうじ', 'おうせつ', 'おうたい', 'おうふく', 'おうべい', 'おうよう', 'おえる', 'おおい', 'おおう', 'おおどおり', 'おおや', 'おおよそ', 'おかえり', 'おかず', 'おがむ', 'おかわり', 'おぎなう', 'おきる', 'おくさま', 'おくじょう', 'おくりがな', 'おくる', 'おくれる', 'おこす', 'おこなう', 'おこる', 'おさえる', 'おさない', 'おさめる', 'おしいれ', 'おしえる', 'おじぎ', 'おじさん', 'おしゃれ', 'おそらく', 'おそわる', 'おたがい', 'おたく', 'おだやか', 'おちつく', 'おっと', 'おつり', 'おでかけ', 'おとしもの', 'おとなしい', 'おどり', 'おどろかす', 'おばさん', 'おまいり', 'おめでとう', 'おもいで', 'おもう', 'おもたい', 'おもちゃ', 'おやつ', 'おやゆび', 'およぼす', 'おらんだ', 'おろす', 'おんがく', 'おんけい', 'おんしゃ', 'おんせん', 'おんだん', 'おんちゅう', 'おんどけい', 'かあつ', 'かいが', 'がいき', 'がいけん', 'がいこう', 'かいさつ', 'かいしゃ', 'かいすいよく', 'かいぜん', 'かいぞうど', 'かいつう', 'かいてん', 'かいとう', 'かいふく', 'がいへき', 'かいほう', 'かいよう', 'がいらい', 'かいわ', 'かえる', 'かおり', 'かかえる', 'かがく', 'かがし', 'かがみ', 'かくご', 'かくとく', 'かざる', 'がぞう', 'かたい', 'かたち', 'がちょう', 'がっきゅう', 'がっこう', 'がっさん', 'がっしょう', 'かなざわし', 'かのう', 'がはく', 'かぶか', 'かほう', 'かほご', 'かまう', 'かまぼこ', 'かめれおん', 'かゆい', 'かようび', 'からい', 'かるい', 'かろう', 'かわく', 'かわら', 'がんか', 'かんけい', 'かんこう', 'かんしゃ', 'かんそう', 'かんたん', 'かんち', 'がんばる', 'きあい', 'きあつ', 'きいろ', 'ぎいん', 'きうい', 'きうん', 'きえる', 'きおう', 'きおく', 'きおち', 'きおん', 'きかい', 'きかく', 'きかんしゃ', 'ききて', 'きくばり', 'きくらげ', 'きけんせい', 'きこう', 'きこえる', 'きこく', 'きさい', 'きさく', 'きさま', 'きさらぎ', 'ぎじかがく', 'ぎしき', 'ぎじたいけん', 'ぎじにってい', 'ぎじゅつしゃ', 'きすう', 'きせい', 'きせき', 'きせつ', 'きそう', 'きぞく', 'きぞん', 'きたえる', 'きちょう', 'きつえん', 'ぎっちり', 'きつつき', 'きつね', 'きてい', 'きどう', 'きどく', 'きない', 'きなが', 'きなこ', 'きぬごし', 'きねん', 'きのう', 'きのした', 'きはく', 'きびしい', 'きひん', 'きふく', 'きぶん', 'きぼう', 'きほん', 'きまる', 'きみつ', 'きむずかしい', 'きめる', 'きもだめし', 'きもち', 'きもの', 'きゃく', 'きやく', 'ぎゅうにく', 'きよう', 'きょうりゅう', 'きらい', 'きらく', 'きりん', 'きれい', 'きれつ', 'きろく', 'ぎろん', 'きわめる', 'ぎんいろ', 'きんかくじ', 'きんじょ', 'きんようび', 'ぐあい', 'くいず', 'くうかん', 'くうき', 'くうぐん', 'くうこう', 'ぐうせい', 'くうそう', 'ぐうたら', 'くうふく', 'くうぼ', 'くかん', 'くきょう', 'くげん', 'ぐこう', 'くさい', 'くさき', 'くさばな', 'くさる', 'くしゃみ', 'くしょう', 'くすのき', 'くすりゆび', 'くせげ', 'くせん', 'ぐたいてき', 'くださる', 'くたびれる', 'くちこみ', 'くちさき', 'くつした', 'ぐっすり', 'くつろぐ', 'くとうてん', 'くどく', 'くなん', 'くねくね', 'くのう', 'くふう', 'くみあわせ', 'くみたてる', 'くめる', 'くやくしょ', 'くらす', 'くらべる', 'くるま', 'くれる', 'くろう', 'くわしい', 'ぐんかん', 'ぐんしょく', 'ぐんたい', 'ぐんて', 'けあな', 'けいかく', 'けいけん', 'けいこ', 'けいさつ', 'げいじゅつ', 'けいたい', 'げいのうじん', 'けいれき', 'けいろ', 'けおとす', 'けおりもの', 'げきか', 'げきげん', 'げきだん', 'げきちん', 'げきとつ', 'げきは', 'げきやく', 'げこう', 'げこくじょう', 'げざい', 'けさき', 'げざん', 'けしき', 'けしごむ', 'けしょう', 'げすと', 'けたば', 'けちゃっぷ', 'けちらす', 'けつあつ', 'けつい', 'けつえき', 'けっこん', 'けつじょ', 'けっせき', 'けってい', 'けつまつ', 'げつようび', 'げつれい', 'けつろん', 'げどく', 'けとばす', 'けとる', 'けなげ', 'けなす', 'けなみ', 'けぬき', 'げねつ', 'けねん', 'けはい', 'げひん', 'けぶかい', 'げぼく', 'けまり', 'けみかる', 'けむし', 'けむり', 'けもの', 'けらい', 'けろけろ', 'けわしい', 'けんい', 'けんえつ', 'けんお', 'けんか', 'げんき', 'けんげん', 'けんこう', 'けんさく', 'けんしゅう', 'けんすう', 'げんそう', 'けんちく', 'けんてい', 'けんとう', 'けんない', 'けんにん', 'げんぶつ', 'けんま', 'けんみん', 'けんめい', 'けんらん', 'けんり', 'こあくま', 'こいぬ', 'こいびと', 'ごうい', 'こうえん', 'こうおん', 'こうかん', 'ごうきゅう', 'ごうけい', 'こうこう', 'こうさい', 'こうじ', 'こうすい', 'ごうせい', 'こうそく', 'こうたい', 'こうちゃ', 'こうつう', 'こうてい', 'こうどう', 'こうない', 'こうはい', 'ごうほう', 'ごうまん', 'こうもく', 'こうりつ', 'こえる', 'こおり', 'ごかい', 'ごがつ', 'ごかん', 'こくご', 'こくさい', 'こくとう', 'こくない', 'こくはく', 'こぐま', 'こけい', 'こける', 'ここのか', 'こころ', 'こさめ', 'こしつ', 'こすう', 'こせい', 'こせき', 'こぜん', 'こそだて', 'こたい', 'こたえる', 'こたつ', 'こちょう', 'こっか', 'こつこつ', 'こつばん', 'こつぶ', 'こてい', 'こてん', 'ことがら', 'ことし', 'ことば', 'ことり', 'こなごな', 'こねこね', 'このまま', 'このみ', 'このよ', 'ごはん', 'こひつじ', 'こふう', 'こふん', 'こぼれる', 'ごまあぶら', 'こまかい', 'ごますり', 'こまつな', 'こまる', 'こむぎこ', 'こもじ', 'こもち', 'こもの', 'こもん', 'こやく', 'こやま', 'こゆう', 'こゆび', 'こよい', 'こよう', 'こりる', 'これくしょん', 'ころっけ', 'こわもて', 'こわれる', 'こんいん', 'こんかい', 'こんき', 'こんしゅう', 'こんすい', 'こんだて', 'こんとん', 'こんなん', 'こんびに', 'こんぽん', 'こんまけ', 'こんや', 'こんれい', 'こんわく', 'ざいえき', 'さいかい', 'さいきん', 'ざいげん', 'ざいこ', 'さいしょ', 'さいせい', 'ざいたく', 'ざいちゅう', 'さいてき', 'ざいりょう', 'さうな', 'さかいし', 'さがす', 'さかな', 'さかみち', 'さがる', 'さぎょう', 'さくし', 'さくひん', 'さくら', 'さこく', 'さこつ', 'さずかる', 'ざせき', 'さたん', 'さつえい', 'ざつおん', 'ざっか', 'ざつがく', 'さっきょく', 'ざっし', 'さつじん', 'ざっそう', 'さつたば', 'さつまいも', 'さてい', 'さといも', 'さとう', 'さとおや', 'さとし', 'さとる', 'さのう', 'さばく', 'さびしい', 'さべつ', 'さほう', 'さほど', 'さます', 'さみしい', 'さみだれ', 'さむけ', 'さめる', 'さやえんどう', 'さゆう', 'さよう', 'さよく', 'さらだ', 'ざるそば', 'さわやか', 'さわる', 'さんいん', 'さんか', 'さんきゃく', 'さんこう', 'さんさい', 'ざんしょ', 'さんすう', 'さんせい', 'さんそ', 'さんち', 'さんま', 'さんみ', 'さんらん', 'しあい', 'しあげ', 'しあさって', 'しあわせ', 'しいく', 'しいん', 'しうち', 'しえい', 'しおけ', 'しかい', 'しかく', 'じかん', 'しごと', 'しすう', 'じだい', 'したうけ', 'したぎ', 'したて', 'したみ', 'しちょう', 'しちりん', 'しっかり', 'しつじ', 'しつもん', 'してい', 'してき', 'してつ', 'じてん', 'じどう', 'しなぎれ', 'しなもの', 'しなん', 'しねま', 'しねん', 'しのぐ', 'しのぶ', 'しはい', 'しばかり', 'しはつ', 'しはらい', 'しはん', 'しひょう', 'しふく', 'じぶん', 'しへい', 'しほう', 'しほん', 'しまう', 'しまる', 'しみん', 'しむける', 'じむしょ', 'しめい', 'しめる', 'しもん', 'しゃいん', 'しゃうん', 'しゃおん', 'じゃがいも', 'しやくしょ', 'しゃくほう', 'しゃけん', 'しゃこ', 'しゃざい', 'しゃしん', 'しゃせん', 'しゃそう', 'しゃたい', 'しゃちょう', 'しゃっきん', 'じゃま', 'しゃりん', 'しゃれい', 'じゆう', 'じゅうしょ', 'しゅくはく', 'じゅしん', 'しゅっせき', 'しゅみ', 'しゅらば', 'じゅんばん', 'しょうかい', 'しょくたく', 'しょっけん', 'しょどう', 'しょもつ', 'しらせる', 'しらべる', 'しんか', 'しんこう', 'じんじゃ', 'しんせいじ', 'しんちく', 'しんりん', 'すあげ', 'すあし', 'すあな', 'ずあん', 'すいえい', 'すいか', 'すいとう', 'ずいぶん', 'すいようび', 'すうがく', 'すうじつ', 'すうせん', 'すおどり', 'すきま', 'すくう', 'すくない', 'すける', 'すごい', 'すこし', 'ずさん', 'すずしい', 'すすむ', 'すすめる', 'すっかり', 'ずっしり', 'ずっと', 'すてき', 'すてる', 'すねる', 'すのこ', 'すはだ', 'すばらしい', 'ずひょう', 'ずぶぬれ', 'すぶり', 'すふれ', 'すべて', 'すべる', 'ずほう', 'すぼん', 'すまい', 'すめし', 'すもう', 'すやき', 'すらすら', 'するめ', 'すれちがう', 'すろっと', 'すわる', 'すんぜん', 'すんぽう', 'せあぶら', 'せいかつ', 'せいげん', 'せいじ', 'せいよう', 'せおう', 'せかいかん', 'せきにん', 'せきむ', 'せきゆ', 'せきらんうん', 'せけん', 'せこう', 'せすじ', 'せたい', 'せたけ', 'せっかく', 'せっきゃく', 'ぜっく', 'せっけん', 'せっこつ', 'せっさたくま', 'せつぞく', 'せつだん', 'せつでん', 'せっぱん', 'せつび', 'せつぶん', 'せつめい', 'せつりつ', 'せなか', 'せのび', 'せはば', 'せびろ', 'せぼね', 'せまい', 'せまる', 'せめる', 'せもたれ', 'せりふ', 'ぜんあく', 'せんい', 'せんえい', 'せんか', 'せんきょ', 'せんく', 'せんげん', 'ぜんご', 'せんさい', 'せんしゅ', 'せんすい', 'せんせい', 'せんぞ', 'せんたく', 'せんちょう', 'せんてい', 'せんとう', 'せんぬき', 'せんねん', 'せんぱい', 'ぜんぶ', 'ぜんぽう', 'せんむ', 'せんめんじょ', 'せんもん', 'せんやく', 'せんゆう', 'せんよう', 'ぜんら', 'ぜんりゃく', 'せんれい', 'せんろ', 'そあく', 'そいとげる', 'そいね', 'そうがんきょう', 'そうき', 'そうご', 'そうしん', 'そうだん', 'そうなん', 'そうび', 'そうめん', 'そうり', 'そえもの', 'そえん', 'そがい', 'そげき', 'そこう', 'そこそこ', 'そざい', 'そしな', 'そせい', 'そせん', 'そそぐ', 'そだてる', 'そつう', 'そつえん', 'そっかん', 'そつぎょう', 'そっけつ', 'そっこう', 'そっせん', 'そっと', 'そとがわ', 'そとづら', 'そなえる', 'そなた', 'そふぼ', 'そぼく', 'そぼろ', 'そまつ', 'そまる', 'そむく', 'そむりえ', 'そめる', 'そもそも', 'そよかぜ', 'そらまめ', 'そろう', 'そんかい', 'そんけい', 'そんざい', 'そんしつ', 'そんぞく', 'そんちょう', 'ぞんび', 'ぞんぶん', 'そんみん', 'たあい', 'たいいん', 'たいうん', 'たいえき', 'たいおう', 'だいがく', 'たいき', 'たいぐう', 'たいけん', 'たいこ', 'たいざい', 'だいじょうぶ', 'だいすき', 'たいせつ', 'たいそう', 'だいたい', 'たいちょう', 'たいてい', 'だいどころ', 'たいない', 'たいねつ', 'たいのう', 'たいはん', 'だいひょう', 'たいふう', 'たいへん', 'たいほ', 'たいまつばな', 'たいみんぐ', 'たいむ', 'たいめん', 'たいやき', 'たいよう', 'たいら', 'たいりょく', 'たいる', 'たいわん', 'たうえ', 'たえる', 'たおす', 'たおる', 'たおれる', 'たかい', 'たかね', 'たきび', 'たくさん', 'たこく', 'たこやき', 'たさい', 'たしざん', 'だじゃれ', 'たすける', 'たずさわる', 'たそがれ', 'たたかう', 'たたく', 'ただしい', 'たたみ', 'たちばな', 'だっかい', 'だっきゃく', 'だっこ', 'だっしゅつ', 'だったい', 'たてる', 'たとえる', 'たなばた', 'たにん', 'たぬき', 'たのしみ', 'たはつ', 'たぶん', 'たべる', 'たぼう', 'たまご', 'たまる', 'だむる', 'ためいき', 'ためす', 'ためる', 'たもつ', 'たやすい', 'たよる', 'たらす', 'たりきほんがん', 'たりょう', 'たりる', 'たると', 'たれる', 'たれんと', 'たろっと', 'たわむれる', 'だんあつ', 'たんい', 'たんおん', 'たんか', 'たんき', 'たんけん', 'たんご', 'たんさん', 'たんじょうび', 'だんせい', 'たんそく', 'たんたい', 'だんち', 'たんてい', 'たんとう', 'だんな', 'たんにん', 'だんねつ', 'たんのう', 'たんぴん', 'だんぼう', 'たんまつ', 'たんめい', 'だんれつ', 'だんろ', 'だんわ', 'ちあい', 'ちあん', 'ちいき', 'ちいさい', 'ちえん', 'ちかい', 'ちから', 'ちきゅう', 'ちきん', 'ちけいず', 'ちけん', 'ちこく', 'ちさい', 'ちしき', 'ちしりょう', 'ちせい', 'ちそう', 'ちたい', 'ちたん', 'ちちおや', 'ちつじょ', 'ちてき', 'ちてん', 'ちぬき', 'ちぬり', 'ちのう', 'ちひょう', 'ちへいせん', 'ちほう', 'ちまた', 'ちみつ', 'ちみどろ', 'ちめいど', 'ちゃんこなべ', 'ちゅうい', 'ちゆりょく', 'ちょうし', 'ちょさくけん', 'ちらし', 'ちらみ', 'ちりがみ', 'ちりょう', 'ちるど', 'ちわわ', 'ちんたい', 'ちんもく', 'ついか', 'ついたち', 'つうか', 'つうじょう', 'つうはん', 'つうわ', 'つかう', 'つかれる', 'つくね', 'つくる', 'つけね', 'つける', 'つごう', 'つたえる', 'つづく', 'つつじ', 'つつむ', 'つとめる', 'つながる', 'つなみ', 'つねづね', 'つのる', 'つぶす', 'つまらない', 'つまる', 'つみき', 'つめたい', 'つもり', 'つもる', 'つよい', 'つるぼ', 'つるみく', 'つわもの', 'つわり', 'てあし', 'てあて', 'てあみ', 'ていおん', 'ていか', 'ていき', 'ていけい', 'ていこく', 'ていさつ', 'ていし', 'ていせい', 'ていたい', 'ていど', 'ていねい', 'ていひょう', 'ていへん', 'ていぼう', 'てうち', 'ておくれ', 'てきとう', 'てくび', 'でこぼこ', 'てさぎょう', 'てさげ', 'てすり', 'てそう', 'てちがい', 'てちょう', 'てつがく', 'てつづき', 'でっぱ', 'てつぼう', 'てつや', 'でぬかえ', 'てぬき', 'てぬぐい', 'てのひら', 'てはい', 'てぶくろ', 'てふだ', 'てほどき', 'てほん', 'てまえ', 'てまきずし', 'てみじか', 'てみやげ', 'てらす', 'てれび', 'てわけ', 'てわたし', 'でんあつ', 'てんいん', 'てんかい', 'てんき', 'てんぐ', 'てんけん', 'てんごく', 'てんさい', 'てんし', 'てんすう', 'でんち', 'てんてき', 'てんとう', 'てんない', 'てんぷら', 'てんぼうだい', 'てんめつ', 'てんらんかい', 'でんりょく', 'でんわ', 'どあい', 'といれ', 'どうかん', 'とうきゅう', 'どうぐ', 'とうし', 'とうむぎ', 'とおい', 'とおか', 'とおく', 'とおす', 'とおる', 'とかい', 'とかす', 'ときおり', 'ときどき', 'とくい', 'とくしゅう', 'とくてん', 'とくに', 'とくべつ', 'とけい', 'とける', 'とこや', 'とさか', 'としょかん', 'とそう', 'とたん', 'とちゅう', 'とっきゅう', 'とっくん', 'とつぜん', 'とつにゅう', 'とどける', 'ととのえる', 'とない', 'となえる', 'となり', 'とのさま', 'とばす', 'どぶがわ', 'とほう', 'とまる', 'とめる', 'ともだち', 'ともる', 'どようび', 'とらえる', 'とんかつ', 'どんぶり', 'ないかく', 'ないこう', 'ないしょ', 'ないす', 'ないせん', 'ないそう', 'なおす', 'ながい', 'なくす', 'なげる', 'なこうど', 'なさけ', 'なたでここ', 'なっとう', 'なつやすみ', 'ななおし', 'なにごと', 'なにもの', 'なにわ', 'なのか', 'なふだ', 'なまいき', 'なまえ', 'なまみ', 'なみだ', 'なめらか', 'なめる', 'なやむ', 'ならう', 'ならび', 'ならぶ', 'なれる', 'なわとび', 'なわばり', 'にあう', 'にいがた', 'にうけ', 'におい', 'にかい', 'にがて', 'にきび', 'にくしみ', 'にくまん', 'にげる', 'にさんかたんそ', 'にしき', 'にせもの', 'にちじょう', 'にちようび', 'にっか', 'にっき', 'にっけい', 'にっこう', 'にっさん', 'にっしょく', 'にっすう', 'にっせき', 'にってい', 'になう', 'にほん', 'にまめ', 'にもつ', 'にやり', 'にゅういん', 'にりんしゃ', 'にわとり', 'にんい', 'にんか', 'にんき', 'にんげん', 'にんしき', 'にんずう', 'にんそう', 'にんたい', 'にんち', 'にんてい', 'にんにく', 'にんぷ', 'にんまり', 'にんむ', 'にんめい', 'にんよう', 'ぬいくぎ', 'ぬかす', 'ぬぐいとる', 'ぬぐう', 'ぬくもり', 'ぬすむ', 'ぬまえび', 'ぬめり', 'ぬらす', 'ぬんちゃく', 'ねあげ', 'ねいき', 'ねいる', 'ねいろ', 'ねぐせ', 'ねくたい', 'ねくら', 'ねこぜ', 'ねこむ', 'ねさげ', 'ねすごす', 'ねそべる', 'ねだん', 'ねつい', 'ねっしん', 'ねつぞう', 'ねったいぎょ', 'ねぶそく', 'ねふだ', 'ねぼう', 'ねほりはほり', 'ねまき', 'ねまわし', 'ねみみ', 'ねむい', 'ねむたい', 'ねもと', 'ねらう', 'ねわざ', 'ねんいり', 'ねんおし', 'ねんかん', 'ねんきん', 'ねんぐ', 'ねんざ', 'ねんし', 'ねんちゃく', 'ねんど', 'ねんぴ', 'ねんぶつ', 'ねんまつ', 'ねんりょう', 'ねんれい', 'のいず', 'のおづま', 'のがす', 'のきなみ', 'のこぎり', 'のこす', 'のこる', 'のせる', 'のぞく', 'のぞむ', 'のたまう', 'のちほど', 'のっく', 'のばす', 'のはら', 'のべる', 'のぼる', 'のみもの', 'のやま', 'のらいぬ', 'のらねこ', 'のりもの', 'のりゆき', 'のれん', 'のんき', 'ばあい', 'はあく', 'ばあさん', 'ばいか', 'ばいく', 'はいけん', 'はいご', 'はいしん', 'はいすい', 'はいせん', 'はいそう', 'はいち', 'ばいばい', 'はいれつ', 'はえる', 'はおる', 'はかい', 'ばかり', 'はかる', 'はくしゅ', 'はけん', 'はこぶ', 'はさみ', 'はさん', 'はしご', 'ばしょ', 'はしる', 'はせる', 'ぱそこん', 'はそん', 'はたん', 'はちみつ', 'はつおん', 'はっかく', 'はづき', 'はっきり', 'はっくつ', 'はっけん', 'はっこう', 'はっさん', 'はっしん', 'はったつ', 'はっちゅう', 'はってん', 'はっぴょう', 'はっぽう', 'はなす', 'はなび', 'はにかむ', 'はぶらし', 'はみがき', 'はむかう', 'はめつ', 'はやい', 'はやし', 'はらう', 'はろうぃん', 'はわい', 'はんい', 'はんえい', 'はんおん', 'はんかく', 'はんきょう', 'ばんぐみ', 'はんこ', 'はんしゃ', 'はんすう', 'はんだん', 'ぱんち', 'ぱんつ', 'はんてい', 'はんとし', 'はんのう', 'はんぱ', 'はんぶん', 'はんぺん', 'はんぼうき', 'はんめい', 'はんらん', 'はんろん', 'ひいき', 'ひうん', 'ひえる', 'ひかく', 'ひかり', 'ひかる', 'ひかん', 'ひくい', 'ひけつ', 'ひこうき', 'ひこく', 'ひさい', 'ひさしぶり', 'ひさん', 'びじゅつかん', 'ひしょ', 'ひそか', 'ひそむ', 'ひたむき', 'ひだり', 'ひたる', 'ひつぎ', 'ひっこし', 'ひっし', 'ひつじゅひん', 'ひっす', 'ひつぜん', 'ぴったり', 'ぴっちり', 'ひつよう', 'ひてい', 'ひとごみ', 'ひなまつり', 'ひなん', 'ひねる', 'ひはん', 'ひびく', 'ひひょう', 'ひほう', 'ひまわり', 'ひまん', 'ひみつ', 'ひめい', 'ひめじし', 'ひやけ', 'ひやす', 'ひよう', 'びょうき', 'ひらがな', 'ひらく', 'ひりつ', 'ひりょう', 'ひるま', 'ひるやすみ', 'ひれい', 'ひろい', 'ひろう', 'ひろき', 'ひろゆき', 'ひんかく', 'ひんけつ', 'ひんこん', 'ひんしゅ', 'ひんそう', 'ぴんち', 'ひんぱん', 'びんぼう', 'ふあん', 'ふいうち', 'ふうけい', 'ふうせん', 'ぷうたろう', 'ふうとう', 'ふうふ', 'ふえる', 'ふおん', 'ふかい', 'ふきん', 'ふくざつ', 'ふくぶくろ', 'ふこう', 'ふさい', 'ふしぎ', 'ふじみ', 'ふすま', 'ふせい', 'ふせぐ', 'ふそく', 'ぶたにく', 'ふたん', 'ふちょう', 'ふつう', 'ふつか', 'ふっかつ', 'ふっき', 'ふっこく', 'ぶどう', 'ふとる', 'ふとん', 'ふのう', 'ふはい', 'ふひょう', 'ふへん', 'ふまん', 'ふみん', 'ふめつ', 'ふめん', 'ふよう', 'ふりこ', 'ふりる', 'ふるい', 'ふんいき', 'ぶんがく', 'ぶんぐ', 'ふんしつ', 'ぶんせき', 'ふんそう', 'ぶんぽう', 'へいあん', 'へいおん', 'へいがい', 'へいき', 'へいげん', 'へいこう', 'へいさ', 'へいしゃ', 'へいせつ', 'へいそ', 'へいたく', 'へいてん', 'へいねつ', 'へいわ', 'へきが', 'へこむ', 'べにいろ', 'べにしょうが', 'へらす', 'へんかん', 'べんきょう', 'べんごし', 'へんさい', 'へんたい', 'べんり', 'ほあん', 'ほいく', 'ぼうぎょ', 'ほうこく', 'ほうそう', 'ほうほう', 'ほうもん', 'ほうりつ', 'ほえる', 'ほおん', 'ほかん', 'ほきょう', 'ぼきん', 'ほくろ', 'ほけつ', 'ほけん', 'ほこう', 'ほこる', 'ほしい', 'ほしつ', 'ほしゅ', 'ほしょう', 'ほせい', 'ほそい', 'ほそく', 'ほたて', 'ほたる', 'ぽちぶくろ', 'ほっきょく', 'ほっさ', 'ほったん', 'ほとんど', 'ほめる', 'ほんい', 'ほんき', 'ほんけ', 'ほんしつ', 'ほんやく', 'まいにち', 'まかい', 'まかせる', 'まがる', 'まける', 'まこと', 'まさつ', 'まじめ', 'ますく', 'まぜる', 'まつり', 'まとめ', 'まなぶ', 'まぬけ', 'まねく', 'まほう', 'まもる', 'まゆげ', 'まよう', 'まろやか', 'まわす', 'まわり', 'まわる', 'まんが', 'まんきつ', 'まんぞく', 'まんなか', 'みいら', 'みうち', 'みえる', 'みがく', 'みかた', 'みかん', 'みけん', 'みこん', 'みじかい', 'みすい', 'みすえる', 'みせる', 'みっか', 'みつかる', 'みつける', 'みてい', 'みとめる', 'みなと', 'みなみかさい', 'みねらる', 'みのう', 'みのがす', 'みほん', 'みもと', 'みやげ', 'みらい', 'みりょく', 'みわく', 'みんか', 'みんぞく', 'むいか', 'むえき', 'むえん', 'むかい', 'むかう', 'むかえ', 'むかし', 'むぎちゃ', 'むける', 'むげん', 'むさぼる', 'むしあつい', 'むしば', 'むじゅん', 'むしろ', 'むすう', 'むすこ', 'むすぶ', 'むすめ', 'むせる', 'むせん', 'むちゅう', 'むなしい', 'むのう', 'むやみ', 'むよう', 'むらさき', 'むりょう', 'むろん', 'めいあん', 'めいうん', 'めいえん', 'めいかく', 'めいきょく', 'めいさい', 'めいし', 'めいそう', 'めいぶつ', 'めいれい', 'めいわく', 'めぐまれる', 'めざす', 'めした', 'めずらしい', 'めだつ', 'めまい', 'めやす', 'めんきょ', 'めんせき', 'めんどう', 'もうしあげる', 'もうどうけん', 'もえる', 'もくし', 'もくてき', 'もくようび', 'もちろん', 'もどる', 'もらう', 'もんく', 'もんだい', 'やおや', 'やける', 'やさい', 'やさしい', 'やすい', 'やすたろう', 'やすみ', 'やせる', 'やそう', 'やたい', 'やちん', 'やっと', 'やっぱり', 'やぶる', 'やめる', 'ややこしい', 'やよい', 'やわらかい', 'ゆうき', 'ゆうびんきょく', 'ゆうべ', 'ゆうめい', 'ゆけつ', 'ゆしゅつ', 'ゆせん', 'ゆそう', 'ゆたか', 'ゆちゃく', 'ゆでる', 'ゆにゅう', 'ゆびわ', 'ゆらい', 'ゆれる', 'ようい', 'ようか', 'ようきゅう', 'ようじ', 'ようす', 'ようちえん', 'よかぜ', 'よかん', 'よきん', 'よくせい', 'よくぼう', 'よけい', 'よごれる', 'よさん', 'よしゅう', 'よそう', 'よそく', 'よっか', 'よてい', 'よどがわく', 'よねつ', 'よやく', 'よゆう', 'よろこぶ', 'よろしい', 'らいう', 'らくがき', 'らくご', 'らくさつ', 'らくだ', 'らしんばん', 'らせん', 'らぞく', 'らたい', 'らっか', 'られつ', 'りえき', 'りかい', 'りきさく', 'りきせつ', 'りくぐん', 'りくつ', 'りけん', 'りこう', 'りせい', 'りそう', 'りそく', 'りてん', 'りねん', 'りゆう', 'りゅうがく', 'りよう', 'りょうり', 'りょかん', 'りょくちゃ', 'りょこう', 'りりく', 'りれき', 'りろん', 'りんご', 'るいけい', 'るいさい', 'るいじ', 'るいせき', 'るすばん', 'るりがわら', 'れいかん', 'れいぎ', 'れいせい', 'れいぞうこ', 'れいとう', 'れいぼう', 'れきし', 'れきだい', 'れんあい', 'れんけい', 'れんこん', 'れんさい', 'れんしゅう', 'れんぞく', 'れんらく', 'ろうか', 'ろうご', 'ろうじん', 'ろうそく', 'ろくが', 'ろこつ', 'ろじうら', 'ろしゅつ', 'ろせん', 'ろてん', 'ろめん', 'ろれつ', 'ろんぎ', 'ろんぱ', 'ろんぶん', 'ろんり', 'わかす', 'わかめ', 'わかやま', 'わかれる', 'わしつ', 'わじまし', 'わすれもの', 'わらう', 'われる']; + +export default japanese; diff --git a/src/backend/wallet/lib/words/spanish.js b/src/backend/wallet/lib/words/spanish.js new file mode 100644 index 0000000..2f60f78 --- /dev/null +++ b/src/backend/wallet/lib/words/spanish.js @@ -0,0 +1,5 @@ +'use strict'; + +var spanish = ['ábaco', 'abdomen', 'abeja', 'abierto', 'abogado', 'abono', 'aborto', 'abrazo', 'abrir', 'abuelo', 'abuso', 'acabar', 'academia', 'acceso', 'acción', 'aceite', 'acelga', 'acento', 'aceptar', 'ácido', 'aclarar', 'acné', 'acoger', 'acoso', 'activo', 'acto', 'actriz', 'actuar', 'acudir', 'acuerdo', 'acusar', 'adicto', 'admitir', 'adoptar', 'adorno', 'aduana', 'adulto', 'aéreo', 'afectar', 'afición', 'afinar', 'afirmar', 'ágil', 'agitar', 'agonía', 'agosto', 'agotar', 'agregar', 'agrio', 'agua', 'agudo', 'águila', 'aguja', 'ahogo', 'ahorro', 'aire', 'aislar', 'ajedrez', 'ajeno', 'ajuste', 'alacrán', 'alambre', 'alarma', 'alba', 'álbum', 'alcalde', 'aldea', 'alegre', 'alejar', 'alerta', 'aleta', 'alfiler', 'alga', 'algodón', 'aliado', 'aliento', 'alivio', 'alma', 'almeja', 'almíbar', 'altar', 'alteza', 'altivo', 'alto', 'altura', 'alumno', 'alzar', 'amable', 'amante', 'amapola', 'amargo', 'amasar', 'ámbar', 'ámbito', 'ameno', 'amigo', 'amistad', 'amor', 'amparo', 'amplio', 'ancho', 'anciano', 'ancla', 'andar', 'andén', 'anemia', 'ángulo', 'anillo', 'ánimo', 'anís', 'anotar', 'antena', 'antiguo', 'antojo', 'anual', 'anular', 'anuncio', 'añadir', 'añejo', 'año', 'apagar', 'aparato', 'apetito', 'apio', 'aplicar', 'apodo', 'aporte', 'apoyo', 'aprender', 'aprobar', 'apuesta', 'apuro', 'arado', 'araña', 'arar', 'árbitro', 'árbol', 'arbusto', 'archivo', 'arco', 'arder', 'ardilla', 'arduo', 'área', 'árido', 'aries', 'armonía', 'arnés', 'aroma', 'arpa', 'arpón', 'arreglo', 'arroz', 'arruga', 'arte', 'artista', 'asa', 'asado', 'asalto', 'ascenso', 'asegurar', 'aseo', 'asesor', 'asiento', 'asilo', 'asistir', 'asno', 'asombro', 'áspero', 'astilla', 'astro', 'astuto', 'asumir', 'asunto', 'atajo', 'ataque', 'atar', 'atento', 'ateo', 'ático', 'atleta', 'átomo', 'atraer', 'atroz', 'atún', 'audaz', 'audio', 'auge', 'aula', 'aumento', 'ausente', 'autor', 'aval', 'avance', 'avaro', 'ave', 'avellana', 'avena', 'avestruz', 'avión', 'aviso', 'ayer', 'ayuda', 'ayuno', 'azafrán', 'azar', 'azote', 'azúcar', 'azufre', 'azul', 'baba', 'babor', 'bache', 'bahía', 'baile', 'bajar', 'balanza', 'balcón', 'balde', 'bambú', 'banco', 'banda', 'baño', 'barba', 'barco', 'barniz', 'barro', 'báscula', 'bastón', 'basura', 'batalla', 'batería', 'batir', 'batuta', 'baúl', 'bazar', 'bebé', 'bebida', 'bello', 'besar', 'beso', 'bestia', 'bicho', 'bien', 'bingo', 'blanco', 'bloque', 'blusa', 'boa', 'bobina', 'bobo', 'boca', 'bocina', 'boda', 'bodega', 'boina', 'bola', 'bolero', 'bolsa', 'bomba', 'bondad', 'bonito', 'bono', 'bonsái', 'borde', 'borrar', 'bosque', 'bote', 'botín', 'bóveda', 'bozal', 'bravo', 'brazo', 'brecha', 'breve', 'brillo', 'brinco', 'brisa', 'broca', 'broma', 'bronce', 'brote', 'bruja', 'brusco', 'bruto', 'buceo', 'bucle', 'bueno', 'buey', 'bufanda', 'bufón', 'búho', 'buitre', 'bulto', 'burbuja', 'burla', 'burro', 'buscar', 'butaca', 'buzón', 'caballo', 'cabeza', 'cabina', 'cabra', 'cacao', 'cadáver', 'cadena', 'caer', 'café', 'caída', 'caimán', 'caja', 'cajón', 'cal', 'calamar', 'calcio', 'caldo', 'calidad', 'calle', 'calma', 'calor', 'calvo', 'cama', 'cambio', 'camello', 'camino', 'campo', 'cáncer', 'candil', 'canela', 'canguro', 'canica', 'canto', 'caña', 'cañón', 'caoba', 'caos', 'capaz', 'capitán', 'capote', 'captar', 'capucha', 'cara', 'carbón', 'cárcel', 'careta', 'carga', 'cariño', 'carne', 'carpeta', 'carro', 'carta', 'casa', 'casco', 'casero', 'caspa', 'castor', 'catorce', 'catre', 'caudal', 'causa', 'cazo', 'cebolla', 'ceder', 'cedro', 'celda', 'célebre', 'celoso', 'célula', 'cemento', 'ceniza', 'centro', 'cerca', 'cerdo', 'cereza', 'cero', 'cerrar', 'certeza', 'césped', 'cetro', 'chacal', 'chaleco', 'champú', 'chancla', 'chapa', 'charla', 'chico', 'chiste', 'chivo', 'choque', 'choza', 'chuleta', 'chupar', 'ciclón', 'ciego', 'cielo', 'cien', 'cierto', 'cifra', 'cigarro', 'cima', 'cinco', 'cine', 'cinta', 'ciprés', 'circo', 'ciruela', 'cisne', 'cita', 'ciudad', 'clamor', 'clan', 'claro', 'clase', 'clave', 'cliente', 'clima', 'clínica', 'cobre', 'cocción', 'cochino', 'cocina', 'coco', 'código', 'codo', 'cofre', 'coger', 'cohete', 'cojín', 'cojo', 'cola', 'colcha', 'colegio', 'colgar', 'colina', 'collar', 'colmo', 'columna', 'combate', 'comer', 'comida', 'cómodo', 'compra', 'conde', 'conejo', 'conga', 'conocer', 'consejo', 'contar', 'copa', 'copia', 'corazón', 'corbata', 'corcho', 'cordón', 'corona', 'correr', 'coser', 'cosmos', 'costa', 'cráneo', 'cráter', 'crear', 'crecer', 'creído', 'crema', 'cría', 'crimen', 'cripta', 'crisis', 'cromo', 'crónica', 'croqueta', 'crudo', 'cruz', 'cuadro', 'cuarto', 'cuatro', 'cubo', 'cubrir', 'cuchara', 'cuello', 'cuento', 'cuerda', 'cuesta', 'cueva', 'cuidar', 'culebra', 'culpa', 'culto', 'cumbre', 'cumplir', 'cuna', 'cuneta', 'cuota', 'cupón', 'cúpula', 'curar', 'curioso', 'curso', 'curva', 'cutis', 'dama', 'danza', 'dar', 'dardo', 'dátil', 'deber', 'débil', 'década', 'decir', 'dedo', 'defensa', 'definir', 'dejar', 'delfín', 'delgado', 'delito', 'demora', 'denso', 'dental', 'deporte', 'derecho', 'derrota', 'desayuno', 'deseo', 'desfile', 'desnudo', 'destino', 'desvío', 'detalle', 'detener', 'deuda', 'día', 'diablo', 'diadema', 'diamante', 'diana', 'diario', 'dibujo', 'dictar', 'diente', 'dieta', 'diez', 'difícil', 'digno', 'dilema', 'diluir', 'dinero', 'directo', 'dirigir', 'disco', 'diseño', 'disfraz', 'diva', 'divino', 'doble', 'doce', 'dolor', 'domingo', 'don', 'donar', 'dorado', 'dormir', 'dorso', 'dos', 'dosis', 'dragón', 'droga', 'ducha', 'duda', 'duelo', 'dueño', 'dulce', 'dúo', 'duque', 'durar', 'dureza', 'duro', 'ébano', 'ebrio', 'echar', 'eco', 'ecuador', 'edad', 'edición', 'edificio', 'editor', 'educar', 'efecto', 'eficaz', 'eje', 'ejemplo', 'elefante', 'elegir', 'elemento', 'elevar', 'elipse', 'élite', 'elixir', 'elogio', 'eludir', 'embudo', 'emitir', 'emoción', 'empate', 'empeño', 'empleo', 'empresa', 'enano', 'encargo', 'enchufe', 'encía', 'enemigo', 'enero', 'enfado', 'enfermo', 'engaño', 'enigma', 'enlace', 'enorme', 'enredo', 'ensayo', 'enseñar', 'entero', 'entrar', 'envase', 'envío', 'época', 'equipo', 'erizo', 'escala', 'escena', 'escolar', 'escribir', 'escudo', 'esencia', 'esfera', 'esfuerzo', 'espada', 'espejo', 'espía', 'esposa', 'espuma', 'esquí', 'estar', 'este', 'estilo', 'estufa', 'etapa', 'eterno', 'ética', 'etnia', 'evadir', 'evaluar', 'evento', 'evitar', 'exacto', 'examen', 'exceso', 'excusa', 'exento', 'exigir', 'exilio', 'existir', 'éxito', 'experto', 'explicar', 'exponer', 'extremo', 'fábrica', 'fábula', 'fachada', 'fácil', 'factor', 'faena', 'faja', 'falda', 'fallo', 'falso', 'faltar', 'fama', 'familia', 'famoso', 'faraón', 'farmacia', 'farol', 'farsa', 'fase', 'fatiga', 'fauna', 'favor', 'fax', 'febrero', 'fecha', 'feliz', 'feo', 'feria', 'feroz', 'fértil', 'fervor', 'festín', 'fiable', 'fianza', 'fiar', 'fibra', 'ficción', 'ficha', 'fideo', 'fiebre', 'fiel', 'fiera', 'fiesta', 'figura', 'fijar', 'fijo', 'fila', 'filete', 'filial', 'filtro', 'fin', 'finca', 'fingir', 'finito', 'firma', 'flaco', 'flauta', 'flecha', 'flor', 'flota', 'fluir', 'flujo', 'flúor', 'fobia', 'foca', 'fogata', 'fogón', 'folio', 'folleto', 'fondo', 'forma', 'forro', 'fortuna', 'forzar', 'fosa', 'foto', 'fracaso', 'frágil', 'franja', 'frase', 'fraude', 'freír', 'freno', 'fresa', 'frío', 'frito', 'fruta', 'fuego', 'fuente', 'fuerza', 'fuga', 'fumar', 'función', 'funda', 'furgón', 'furia', 'fusil', 'fútbol', 'futuro', 'gacela', 'gafas', 'gaita', 'gajo', 'gala', 'galería', 'gallo', 'gamba', 'ganar', 'gancho', 'ganga', 'ganso', 'garaje', 'garza', 'gasolina', 'gastar', 'gato', 'gavilán', 'gemelo', 'gemir', 'gen', 'género', 'genio', 'gente', 'geranio', 'gerente', 'germen', 'gesto', 'gigante', 'gimnasio', 'girar', 'giro', 'glaciar', 'globo', 'gloria', 'gol', 'golfo', 'goloso', 'golpe', 'goma', 'gordo', 'gorila', 'gorra', 'gota', 'goteo', 'gozar', 'grada', 'gráfico', 'grano', 'grasa', 'gratis', 'grave', 'grieta', 'grillo', 'gripe', 'gris', 'grito', 'grosor', 'grúa', 'grueso', 'grumo', 'grupo', 'guante', 'guapo', 'guardia', 'guerra', 'guía', 'guiño', 'guion', 'guiso', 'guitarra', 'gusano', 'gustar', 'haber', 'hábil', 'hablar', 'hacer', 'hacha', 'hada', 'hallar', 'hamaca', 'harina', 'haz', 'hazaña', 'hebilla', 'hebra', 'hecho', 'helado', 'helio', 'hembra', 'herir', 'hermano', 'héroe', 'hervir', 'hielo', 'hierro', 'hígado', 'higiene', 'hijo', 'himno', 'historia', 'hocico', 'hogar', 'hoguera', 'hoja', 'hombre', 'hongo', 'honor', 'honra', 'hora', 'hormiga', 'horno', 'hostil', 'hoyo', 'hueco', 'huelga', 'huerta', 'hueso', 'huevo', 'huida', 'huir', 'humano', 'húmedo', 'humilde', 'humo', 'hundir', 'huracán', 'hurto', 'icono', 'ideal', 'idioma', 'ídolo', 'iglesia', 'iglú', 'igual', 'ilegal', 'ilusión', 'imagen', 'imán', 'imitar', 'impar', 'imperio', 'imponer', 'impulso', 'incapaz', 'índice', 'inerte', 'infiel', 'informe', 'ingenio', 'inicio', 'inmenso', 'inmune', 'innato', 'insecto', 'instante', 'interés', 'íntimo', 'intuir', 'inútil', 'invierno', 'ira', 'iris', 'ironía', 'isla', 'islote', 'jabalí', 'jabón', 'jamón', 'jarabe', 'jardín', 'jarra', 'jaula', 'jazmín', 'jefe', 'jeringa', 'jinete', 'jornada', 'joroba', 'joven', 'joya', 'juerga', 'jueves', 'juez', 'jugador', 'jugo', 'juguete', 'juicio', 'junco', 'jungla', 'junio', 'juntar', 'júpiter', 'jurar', 'justo', 'juvenil', 'juzgar', 'kilo', 'koala', 'labio', 'lacio', 'lacra', 'lado', 'ladrón', 'lagarto', 'lágrima', 'laguna', 'laico', 'lamer', 'lámina', 'lámpara', 'lana', 'lancha', 'langosta', 'lanza', 'lápiz', 'largo', 'larva', 'lástima', 'lata', 'látex', 'latir', 'laurel', 'lavar', 'lazo', 'leal', 'lección', 'leche', 'lector', 'leer', 'legión', 'legumbre', 'lejano', 'lengua', 'lento', 'leña', 'león', 'leopardo', 'lesión', 'letal', 'letra', 'leve', 'leyenda', 'libertad', 'libro', 'licor', 'líder', 'lidiar', 'lienzo', 'liga', 'ligero', 'lima', 'límite', 'limón', 'limpio', 'lince', 'lindo', 'línea', 'lingote', 'lino', 'linterna', 'líquido', 'liso', 'lista', 'litera', 'litio', 'litro', 'llaga', 'llama', 'llanto', 'llave', 'llegar', 'llenar', 'llevar', 'llorar', 'llover', 'lluvia', 'lobo', 'loción', 'loco', 'locura', 'lógica', 'logro', 'lombriz', 'lomo', 'lonja', 'lote', 'lucha', 'lucir', 'lugar', 'lujo', 'luna', 'lunes', 'lupa', 'lustro', 'luto', 'luz', 'maceta', 'macho', 'madera', 'madre', 'maduro', 'maestro', 'mafia', 'magia', 'mago', 'maíz', 'maldad', 'maleta', 'malla', 'malo', 'mamá', 'mambo', 'mamut', 'manco', 'mando', 'manejar', 'manga', 'maniquí', 'manjar', 'mano', 'manso', 'manta', 'mañana', 'mapa', 'máquina', 'mar', 'marco', 'marea', 'marfil', 'margen', 'marido', 'mármol', 'marrón', 'martes', 'marzo', 'masa', 'máscara', 'masivo', 'matar', 'materia', 'matiz', 'matriz', 'máximo', 'mayor', 'mazorca', 'mecha', 'medalla', 'medio', 'médula', 'mejilla', 'mejor', 'melena', 'melón', 'memoria', 'menor', 'mensaje', 'mente', 'menú', 'mercado', 'merengue', 'mérito', 'mes', 'mesón', 'meta', 'meter', 'método', 'metro', 'mezcla', 'miedo', 'miel', 'miembro', 'miga', 'mil', 'milagro', 'militar', 'millón', 'mimo', 'mina', 'minero', 'mínimo', 'minuto', 'miope', 'mirar', 'misa', 'miseria', 'misil', 'mismo', 'mitad', 'mito', 'mochila', 'moción', 'moda', 'modelo', 'moho', 'mojar', 'molde', 'moler', 'molino', 'momento', 'momia', 'monarca', 'moneda', 'monja', 'monto', 'moño', 'morada', 'morder', 'moreno', 'morir', 'morro', 'morsa', 'mortal', 'mosca', 'mostrar', 'motivo', 'mover', 'móvil', 'mozo', 'mucho', 'mudar', 'mueble', 'muela', 'muerte', 'muestra', 'mugre', 'mujer', 'mula', 'muleta', 'multa', 'mundo', 'muñeca', 'mural', 'muro', 'músculo', 'museo', 'musgo', 'música', 'muslo', 'nácar', 'nación', 'nadar', 'naipe', 'naranja', 'nariz', 'narrar', 'nasal', 'natal', 'nativo', 'natural', 'náusea', 'naval', 'nave', 'navidad', 'necio', 'néctar', 'negar', 'negocio', 'negro', 'neón', 'nervio', 'neto', 'neutro', 'nevar', 'nevera', 'nicho', 'nido', 'niebla', 'nieto', 'niñez', 'niño', 'nítido', 'nivel', 'nobleza', 'noche', 'nómina', 'noria', 'norma', 'norte', 'nota', 'noticia', 'novato', 'novela', 'novio', 'nube', 'nuca', 'núcleo', 'nudillo', 'nudo', 'nuera', 'nueve', 'nuez', 'nulo', 'número', 'nutria', 'oasis', 'obeso', 'obispo', 'objeto', 'obra', 'obrero', 'observar', 'obtener', 'obvio', 'oca', 'ocaso', 'océano', 'ochenta', 'ocho', 'ocio', 'ocre', 'octavo', 'octubre', 'oculto', 'ocupar', 'ocurrir', 'odiar', 'odio', 'odisea', 'oeste', 'ofensa', 'oferta', 'oficio', 'ofrecer', 'ogro', 'oído', 'oír', 'ojo', 'ola', 'oleada', 'olfato', 'olivo', 'olla', 'olmo', 'olor', 'olvido', 'ombligo', 'onda', 'onza', 'opaco', 'opción', 'ópera', 'opinar', 'oponer', 'optar', 'óptica', 'opuesto', 'oración', 'orador', 'oral', 'órbita', 'orca', 'orden', 'oreja', 'órgano', 'orgía', 'orgullo', 'oriente', 'origen', 'orilla', 'oro', 'orquesta', 'oruga', 'osadía', 'oscuro', 'osezno', 'oso', 'ostra', 'otoño', 'otro', 'oveja', 'óvulo', 'óxido', 'oxígeno', 'oyente', 'ozono', 'pacto', 'padre', 'paella', 'página', 'pago', 'país', 'pájaro', 'palabra', 'palco', 'paleta', 'pálido', 'palma', 'paloma', 'palpar', 'pan', 'panal', 'pánico', 'pantera', 'pañuelo', 'papá', 'papel', 'papilla', 'paquete', 'parar', 'parcela', 'pared', 'parir', 'paro', 'párpado', 'parque', 'párrafo', 'parte', 'pasar', 'paseo', 'pasión', 'paso', 'pasta', 'pata', 'patio', 'patria', 'pausa', 'pauta', 'pavo', 'payaso', 'peatón', 'pecado', 'pecera', 'pecho', 'pedal', 'pedir', 'pegar', 'peine', 'pelar', 'peldaño', 'pelea', 'peligro', 'pellejo', 'pelo', 'peluca', 'pena', 'pensar', 'peñón', 'peón', 'peor', 'pepino', 'pequeño', 'pera', 'percha', 'perder', 'pereza', 'perfil', 'perico', 'perla', 'permiso', 'perro', 'persona', 'pesa', 'pesca', 'pésimo', 'pestaña', 'pétalo', 'petróleo', 'pez', 'pezuña', 'picar', 'pichón', 'pie', 'piedra', 'pierna', 'pieza', 'pijama', 'pilar', 'piloto', 'pimienta', 'pino', 'pintor', 'pinza', 'piña', 'piojo', 'pipa', 'pirata', 'pisar', 'piscina', 'piso', 'pista', 'pitón', 'pizca', 'placa', 'plan', 'plata', 'playa', 'plaza', 'pleito', 'pleno', 'plomo', 'pluma', 'plural', 'pobre', 'poco', 'poder', 'podio', 'poema', 'poesía', 'poeta', 'polen', 'policía', 'pollo', 'polvo', 'pomada', 'pomelo', 'pomo', 'pompa', 'poner', 'porción', 'portal', 'posada', 'poseer', 'posible', 'poste', 'potencia', 'potro', 'pozo', 'prado', 'precoz', 'pregunta', 'premio', 'prensa', 'preso', 'previo', 'primo', 'príncipe', 'prisión', 'privar', 'proa', 'probar', 'proceso', 'producto', 'proeza', 'profesor', 'programa', 'prole', 'promesa', 'pronto', 'propio', 'próximo', 'prueba', 'público', 'puchero', 'pudor', 'pueblo', 'puerta', 'puesto', 'pulga', 'pulir', 'pulmón', 'pulpo', 'pulso', 'puma', 'punto', 'puñal', 'puño', 'pupa', 'pupila', 'puré', 'quedar', 'queja', 'quemar', 'querer', 'queso', 'quieto', 'química', 'quince', 'quitar', 'rábano', 'rabia', 'rabo', 'ración', 'radical', 'raíz', 'rama', 'rampa', 'rancho', 'rango', 'rapaz', 'rápido', 'rapto', 'rasgo', 'raspa', 'rato', 'rayo', 'raza', 'razón', 'reacción', 'realidad', 'rebaño', 'rebote', 'recaer', 'receta', 'rechazo', 'recoger', 'recreo', 'recto', 'recurso', 'red', 'redondo', 'reducir', 'reflejo', 'reforma', 'refrán', 'refugio', 'regalo', 'regir', 'regla', 'regreso', 'rehén', 'reino', 'reír', 'reja', 'relato', 'relevo', 'relieve', 'relleno', 'reloj', 'remar', 'remedio', 'remo', 'rencor', 'rendir', 'renta', 'reparto', 'repetir', 'reposo', 'reptil', 'res', 'rescate', 'resina', 'respeto', 'resto', 'resumen', 'retiro', 'retorno', 'retrato', 'reunir', 'revés', 'revista', 'rey', 'rezar', 'rico', 'riego', 'rienda', 'riesgo', 'rifa', 'rígido', 'rigor', 'rincón', 'riñón', 'río', 'riqueza', 'risa', 'ritmo', 'rito', 'rizo', 'roble', 'roce', 'rociar', 'rodar', 'rodeo', 'rodilla', 'roer', 'rojizo', 'rojo', 'romero', 'romper', 'ron', 'ronco', 'ronda', 'ropa', 'ropero', 'rosa', 'rosca', 'rostro', 'rotar', 'rubí', 'rubor', 'rudo', 'rueda', 'rugir', 'ruido', 'ruina', 'ruleta', 'rulo', 'rumbo', 'rumor', 'ruptura', 'ruta', 'rutina', 'sábado', 'saber', 'sabio', 'sable', 'sacar', 'sagaz', 'sagrado', 'sala', 'saldo', 'salero', 'salir', 'salmón', 'salón', 'salsa', 'salto', 'salud', 'salvar', 'samba', 'sanción', 'sandía', 'sanear', 'sangre', 'sanidad', 'sano', 'santo', 'sapo', 'saque', 'sardina', 'sartén', 'sastre', 'satán', 'sauna', 'saxofón', 'sección', 'seco', 'secreto', 'secta', 'sed', 'seguir', 'seis', 'sello', 'selva', 'semana', 'semilla', 'senda', 'sensor', 'señal', 'señor', 'separar', 'sepia', 'sequía', 'ser', 'serie', 'sermón', 'servir', 'sesenta', 'sesión', 'seta', 'setenta', 'severo', 'sexo', 'sexto', 'sidra', 'siesta', 'siete', 'siglo', 'signo', 'sílaba', 'silbar', 'silencio', 'silla', 'símbolo', 'simio', 'sirena', 'sistema', 'sitio', 'situar', 'sobre', 'socio', 'sodio', 'sol', 'solapa', 'soldado', 'soledad', 'sólido', 'soltar', 'solución', 'sombra', 'sondeo', 'sonido', 'sonoro', 'sonrisa', 'sopa', 'soplar', 'soporte', 'sordo', 'sorpresa', 'sorteo', 'sostén', 'sótano', 'suave', 'subir', 'suceso', 'sudor', 'suegra', 'suelo', 'sueño', 'suerte', 'sufrir', 'sujeto', 'sultán', 'sumar', 'superar', 'suplir', 'suponer', 'supremo', 'sur', 'surco', 'sureño', 'surgir', 'susto', 'sutil', 'tabaco', 'tabique', 'tabla', 'tabú', 'taco', 'tacto', 'tajo', 'talar', 'talco', 'talento', 'talla', 'talón', 'tamaño', 'tambor', 'tango', 'tanque', 'tapa', 'tapete', 'tapia', 'tapón', 'taquilla', 'tarde', 'tarea', 'tarifa', 'tarjeta', 'tarot', 'tarro', 'tarta', 'tatuaje', 'tauro', 'taza', 'tazón', 'teatro', 'techo', 'tecla', 'técnica', 'tejado', 'tejer', 'tejido', 'tela', 'teléfono', 'tema', 'temor', 'templo', 'tenaz', 'tender', 'tener', 'tenis', 'tenso', 'teoría', 'terapia', 'terco', 'término', 'ternura', 'terror', 'tesis', 'tesoro', 'testigo', 'tetera', 'texto', 'tez', 'tibio', 'tiburón', 'tiempo', 'tienda', 'tierra', 'tieso', 'tigre', 'tijera', 'tilde', 'timbre', 'tímido', 'timo', 'tinta', 'tío', 'típico', 'tipo', 'tira', 'tirón', 'titán', 'títere', 'título', 'tiza', 'toalla', 'tobillo', 'tocar', 'tocino', 'todo', 'toga', 'toldo', 'tomar', 'tono', 'tonto', 'topar', 'tope', 'toque', 'tórax', 'torero', 'tormenta', 'torneo', 'toro', 'torpedo', 'torre', 'torso', 'tortuga', 'tos', 'tosco', 'toser', 'tóxico', 'trabajo', 'tractor', 'traer', 'tráfico', 'trago', 'traje', 'tramo', 'trance', 'trato', 'trauma', 'trazar', 'trébol', 'tregua', 'treinta', 'tren', 'trepar', 'tres', 'tribu', 'trigo', 'tripa', 'triste', 'triunfo', 'trofeo', 'trompa', 'tronco', 'tropa', 'trote', 'trozo', 'truco', 'trueno', 'trufa', 'tubería', 'tubo', 'tuerto', 'tumba', 'tumor', 'túnel', 'túnica', 'turbina', 'turismo', 'turno', 'tutor', 'ubicar', 'úlcera', 'umbral', 'unidad', 'unir', 'universo', 'uno', 'untar', 'uña', 'urbano', 'urbe', 'urgente', 'urna', 'usar', 'usuario', 'útil', 'utopía', 'uva', 'vaca', 'vacío', 'vacuna', 'vagar', 'vago', 'vaina', 'vajilla', 'vale', 'válido', 'valle', 'valor', 'válvula', 'vampiro', 'vara', 'variar', 'varón', 'vaso', 'vecino', 'vector', 'vehículo', 'veinte', 'vejez', 'vela', 'velero', 'veloz', 'vena', 'vencer', 'venda', 'veneno', 'vengar', 'venir', 'venta', 'venus', 'ver', 'verano', 'verbo', 'verde', 'vereda', 'verja', 'verso', 'verter', 'vía', 'viaje', 'vibrar', 'vicio', 'víctima', 'vida', 'vídeo', 'vidrio', 'viejo', 'viernes', 'vigor', 'vil', 'villa', 'vinagre', 'vino', 'viñedo', 'violín', 'viral', 'virgo', 'virtud', 'visor', 'víspera', 'vista', 'vitamina', 'viudo', 'vivaz', 'vivero', 'vivir', 'vivo', 'volcán', 'volumen', 'volver', 'voraz', 'votar', 'voto', 'voz', 'vuelo', 'vulgar', 'yacer', 'yate', 'yegua', 'yema', 'yerno', 'yeso', 'yodo', 'yoga', 'yogur', 'zafiro', 'zanja', 'zapato', 'zarza', 'zona', 'zorro', 'zumo', 'zurdo']; + +export default spanish; diff --git a/src/backend/wallet/state-store.js b/src/backend/wallet/state-store.js index dc1d90e..8450eec 100755 --- a/src/backend/wallet/state-store.js +++ b/src/backend/wallet/state-store.js @@ -1,5 +1,5 @@ import localStorage from './util/local-storage' -const ObservableStore = require('obs-store') +import ObservableStore from 'obs-store' export const CONFIG_STORAGE_KEY = 'wicc-wallet-config' diff --git a/src/backend/wallet/wicc-api.js b/src/backend/wallet/wicc-api.js index c61e41a..4436097 100755 --- a/src/backend/wallet/wicc-api.js +++ b/src/backend/wallet/wicc-api.js @@ -1,11 +1,100 @@ import WiccWalletLib from 'wicc-wallet-lib' -import {Decimal} from 'decimal.js'; +import { Decimal } from 'decimal.js'; +import registeraccounttx from './lib/transaction/registeraccounttx' +import commontx from './lib/transaction/commontx' +import contracttx from './lib/transaction/contracttx' +import registerapptx from './lib/transaction/registerapptx' +import delegatetx from './lib/transaction/delegatetx' +import assetcreatetx from './lib/transaction/assetcreatetx' +import assetupdatetx from './lib/transaction/assetupdatetx' +import ucointransfertx from './lib/transaction/ucointransfertx' +import ucontractinvoketx from './lib/transaction/ucontractinvoketx' +import cdpstaketx from './lib/transaction/cdpstaketx' +import cdpredeemtx from './lib/transaction/cdpredeemtx' +import cdpliquidatetx from './lib/transaction/cdpliquidatetx' +import dexbuylimitordertx from './lib/transaction/dexbuylimitordertx' +import dexselllimitordertx from './lib/transaction/dexselllimitordertx' +import dexbuymarketordertx from './lib/transaction/dexbuymarketordertx' +import dexsellmarketordertx from './lib/transaction/dexsellmarketordertx' +import dexcancelordertx from './lib/transaction/dexcancelordertx' const DEFAULT_PASSWORD = '12345678' var Hash = WiccWalletLib.bitcore.crypto.Hash var ECDSA = WiccWalletLib.bitcore.crypto.ECDSA var WriterHelper = WiccWalletLib.bitcore.util.WriterHelper var bitcore = WiccWalletLib.bitcore +var txMap = { + 2: { + txName: 'ACCOUNT_REGISTER_TX', + txAction: registeraccounttx + }, + 3: { + txName: 'BCOIN_TRANSFER_TX', + txAction: commontx + }, + 4: { + txName: 'LCONTRACT_INVOKE_TX', + txAction: contracttx + }, + 5: { + txName: 'LCONTRACT_DEPLOY_TX', + txAction: registerapptx + }, + 6: { + txName: 'DELEGATE_VOTE_TX', + txAction: delegatetx + }, + 9: { + txName: 'ASSET_ISSUE_TX', + txAction: assetcreatetx + }, + 10: { + txName: 'ASSET_UPDATE_TX', + txAction: assetupdatetx + }, + + 11: { + txName: 'UCOIN_TRANSFER_TX', + txAction: ucointransfertx //ucointransfertx + }, + 15: { + txName: 'UCOIN_CONTRACT_INVOKE_TX', + txAction: ucontractinvoketx + }, + 21: { + txName: 'CDP_STAKE_TX', + txAction: cdpstaketx + }, + 22: { + txName: 'CDP_REDEEMP_TX', + txAction: cdpredeemtx + }, + 23: { + txName: 'CDP_LIQUIDATE_TX', + txAction: cdpliquidatetx + }, + 84: { + txName: 'DEX_LIMIT_BUY_ORDER_TX', + txAction: dexbuylimitordertx + }, + 85: { + txName: ' DEX_LIMIT_SELL_ORDER_TX', + txAction: dexselllimitordertx + }, + 86: { + txName: 'DEX_MARKET_BUY_ORDER_TX', + txAction: dexbuymarketordertx + }, + 87: { + txName: 'DEX_MARKET_SELL_ORDER_TX', + txAction: dexsellmarketordertx + }, + 88: { + txName: 'DEX_CANCEL_ORDER_TX', + txAction: dexcancelordertx + } +} + export default class { constructor(network) { this.network = network || 'testnet' @@ -75,7 +164,7 @@ export default class { } createRegisterAppSign(privateKey, height, regAcctId, fees, script, scriptDesc) { - + const v = this.handelNum(fees) const txInfo = { nTxType: 5, @@ -87,25 +176,30 @@ export default class { scriptDesc, publicKey: privateKey.toPublicKey().toString(), } - return this.api.createSignTransaction(privateKey, txInfo) + var txConstructor = txMap[txInfo.nTxType].txAction + var txBody = new txConstructor(txInfo); + return txBody.SerializeTx(privateKey) + // return this.api.createSignTransaction(privateKey, txInfo) } createContractSign(privateKey, height, srcRegId, destRegId, value, fees, contract) { - + const v = this.handelNum(value) const txInfo = { nTxType: 4, nVersion: 1, nValidHeight: height, - fees:this.handelNum(fees) + Math.round(Math.random() * 10), + fees: this.handelNum(fees) + Math.round(Math.random() * 10), srcRegId, destRegId, value: v, vContract: contract, publicKey: privateKey.toPublicKey().toString(), } - - return this.api.createSignTransaction(privateKey, txInfo) + var txConstructor = txMap[txInfo.nTxType].txAction + var txBody = new txConstructor(txInfo); + return txBody.SerializeTx(privateKey) + // return this.api.createSignTransaction(privateKey, txInfo) } createTxSign(privateKey, height, srcRegId, destAddr, value, fees) { @@ -121,8 +215,10 @@ export default class { memo: "memo", publicKey: privateKey.toPublicKey().toString(), } - - return this.api.createSignTransaction(privateKey, commonTxinfo) + var txConstructor = txMap[commonTxinfo.nTxType].txAction + var txBody = new txConstructor(commonTxinfo); + return txBody.SerializeTx(privateKey) + // return this.api.createSignTransaction(privateKey, commonTxinfo) } /** @@ -136,7 +232,7 @@ export default class { "value": v, } ] - + const txInfo = { nTxType: 11, nVersion: 1, @@ -149,8 +245,10 @@ export default class { publicKey: privateKey.toPublicKey().toString(), feesCoinType: feeSymbol, } - - return this.api.createSignTransaction(privateKey, txInfo) + var txConstructor = txMap[txInfo.nTxType].txAction + var txBody = new txConstructor(txInfo); + return txBody.SerializeTx(privateKey) + // return this.api.createSignTransaction(privateKey, txInfo) } createDelegateTxSign(privateKey, height, srcRegId, delegateData, fees) { @@ -163,13 +261,15 @@ export default class { delegateData, publicKey: privateKey.toPublicKey().toString(), } - - return this.api.createSignTransaction(privateKey, txInfo) + var txConstructor = txMap[txInfo.nTxType].txAction + var txBody = new txConstructor(txInfo); + return txBody.SerializeTx(privateKey) + // return this.api.createSignTransaction(privateKey, txInfo) } cdpStake(info) { - + var map = new Map([[info.bcoin_symbol ? info.bcoin_symbol : 'WICC', info.bcoinsToStake]]) var cdpStakeTxinfo = { nTxType: 21, @@ -185,8 +285,10 @@ export default class { network: info.network, scoin_symbol: info.scoin_symbol ? info.scoin_symbol : 'WUSD', }; - - return this.api.createSignTransaction(info.privateKey, cdpStakeTxinfo) + var txConstructor = txMap[cdpStakeTxinfo.nTxType].txAction + var txBody = new txConstructor(cdpStakeTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, cdpStakeTxinfo) } cdpliquidate(info) { @@ -203,8 +305,10 @@ export default class { scoinsToLiquidate: info.scoinsToLiquidate, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, cdpliquidateTxinfo) + var txConstructor = txMap[cdpliquidateTxinfo.nTxType].txAction + var txBody = new txConstructor(cdpliquidateTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, cdpliquidateTxinfo) } cdpRedeem(info) { var map = new Map([[info.bcoins_symbol ? info.bcoins_symbol : "WICC", info.bcoins_to_redeem]]) @@ -221,8 +325,10 @@ export default class { assetMap: map, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, cdpRedeemTxinfo) + var txConstructor = txMap[cdpRedeemTxinfo.nTxType].txAction + var txBody = new txConstructor(cdpRedeemTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, cdpRedeemTxinfo) } @@ -243,12 +349,14 @@ export default class { askPrice: info.askPrice, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, newInfo) + var txConstructor = txMap[newInfo.nTxType].txAction + var txBody = new txConstructor(newInfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, newInfo) } dexPriceBuy(info) { - + var dexBuyLimitTxinfo = { nTxType: 84, nVersion: 1, @@ -263,8 +371,10 @@ export default class { bidPrice: info.bidPrice, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, dexBuyLimitTxinfo) + var txConstructor = txMap[dexBuyLimitTxinfo.nTxType].txAction + var txBody = new txConstructor(dexBuyLimitTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, dexBuyLimitTxinfo) } dexMarketSell(info) { @@ -281,8 +391,10 @@ export default class { assetAmount: info.assetAmount, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, dexSellMarketTxinfo) + var txConstructor = txMap[dexSellMarketTxinfo.nTxType].txAction + var txBody = new txConstructor(dexSellMarketTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, dexSellMarketTxinfo) } dexMarketBuy(info) { @@ -299,8 +411,10 @@ export default class { coinAmount: info.coinAmount, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, dexBuyMarketTxinfo) + var txConstructor = txMap[dexBuyMarketTxinfo.nTxType].txAction + var txBody = new txConstructor(dexBuyMarketTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, dexBuyMarketTxinfo) } dexCancel(info) { var dexCancelTxinfo = { @@ -314,8 +428,10 @@ export default class { orderId: info.orderId, network: info.network }; - - return this.api.createSignTransaction(info.privateKey, dexCancelTxinfo) + var txConstructor = txMap[dexCancelTxinfo.nTxType].txAction + var txBody = new txConstructor(dexCancelTxinfo); + return txBody.SerializeTx(info.privateKey) + // return this.api.createSignTransaction(info.privateKey, dexCancelTxinfo) } assetsPub(info) { var assetData = { @@ -335,8 +451,11 @@ export default class { // publicKey: info.privateKey.toPublicKey().toString(), fees: parseInt(info.fees), // fees pay for miner min 500 wicc }; - const rawtx = this.api.createSignTransaction(info.privateKey, assetCreateInfo) - return rawtx + var txConstructor = txMap[assetCreateInfo.nTxType].txAction + var txBody = new txConstructor(assetCreateInfo); + return txBody.SerializeTx(info.privateKey) + // const rawtx = this.api.createSignTransaction(info.privateKey, assetCreateInfo) + // return rawtx } assetsUpdate(info) { @@ -363,8 +482,11 @@ export default class { fees: parseInt(info.fees), // fees pay for miner min 500 wicc }; // alert(JSON.stringify(assetCreateInfo)) - const rawtx = this.api.createSignTransaction(info.privateKey, assetCreateInfo) - return rawtx + var txConstructor = txMap[assetCreateInfo.nTxType].txAction + var txBody = new txConstructor(assetCreateInfo); + return txBody.SerializeTx(info.privateKey) + // const rawtx = this.api.createSignTransaction(info.privateKey, assetCreateInfo) + // return rawtx } messageSign(msg, privateKey) { @@ -402,12 +524,15 @@ export default class { vContract: contract // contract method, hex format string }; // alert(JSON.stringify(invokeAppInfo)) - var rawtx = this.api.createSignTransaction(privateKey, invokeAppInfo) - // alert(rawtx) - return rawtx + var txConstructor = txMap[invokeAppInfo.nTxType].txAction + var txBody = new txConstructor(invokeAppInfo); + return txBody.SerializeTx(privateKey) + // var rawtx = this.api.createSignTransaction(privateKey, invokeAppInfo) + // // alert(rawtx) + // return rawtx } - handelNum(x){ + handelNum(x) { const a = new Decimal(x) const b = new Decimal(100000000) const v = a.mul(b) diff --git a/src/content/index.js b/src/content/index.js index 0933a62..cc8d642 100755 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,5 +1,5 @@ -const PostMessageStream = require('post-message-stream') -const uuidv4 = require('uuid/v4') +import PostMessageStream from 'post-message-stream'; +import uuidv4 from 'uuid/v4'; const pageStream = new PostMessageStream({ name: 'inpage', target: 'contentscript' @@ -156,85 +156,85 @@ window.WiccWallet = { }, ///创建 - CdpStake(assetMap,sCoinSymbol,scoinNum,cdpTxID,callback){ + CdpStake(assetMap, sCoinSymbol, scoinNum, cdpTxID, callback) { const callbackId = saveCallback(callback) - if (cdpTxID){ + if (cdpTxID) { return send('additionalCdpView', { - wiccNum:assetMap[0].bCoinToStake, - wusdNum:scoinNum, - cdpTxId:cdpTxID, - bcoinSymbol:assetMap[0].coinSymbol, - scoinSymbol:sCoinSymbol, + wiccNum: assetMap[0].bCoinToStake, + wusdNum: scoinNum, + cdpTxId: cdpTxID, + bcoinSymbol: assetMap[0].coinSymbol, + scoinSymbol: sCoinSymbol, callbackId }) } return send('createCdpView', { - wiccNum:assetMap[0].bCoinToStake, - wusdNum:scoinNum, - bcoinSymbol:assetMap[0].coinSymbol, - scoinSymbol:sCoinSymbol, + wiccNum: assetMap[0].bCoinToStake, + wusdNum: scoinNum, + bcoinSymbol: assetMap[0].coinSymbol, + scoinSymbol: sCoinSymbol, callbackId }) }, ///清算 - CdpLiquidate(liquidateAssetSymbol,scoinsToLiquidate,cdpTxId,callback){ + CdpLiquidate(liquidateAssetSymbol, scoinsToLiquidate, cdpTxId, callback) { const callbackId = saveCallback(callback) return send('liquidCdpView', { - assetSymbol:liquidateAssetSymbol, - wusdNum:scoinsToLiquidate, + assetSymbol: liquidateAssetSymbol, + wusdNum: scoinsToLiquidate, cdpTxId, callbackId }) }, ///赎回 - CdpRedeem(assetMap,repayNum,cdpTxId,callback){ + CdpRedeem(assetMap, repayNum, cdpTxId, callback) { const callbackId = saveCallback(callback) return send('redeemCdpView', { - wusdNum:repayNum, - wiccNum:assetMap[0].redeemCoinNum, - redeemSymbol:assetMap[0].coinSymbol, + wusdNum: repayNum, + wiccNum: assetMap[0].redeemCoinNum, + redeemSymbol: assetMap[0].coinSymbol, cdpTxId, callbackId }) }, - + ///限价交易 dealType : Limit_BUY / Limit_SELL - DexLimit(dexTxType,coinType,assetType,amount,price,callback){ + DexLimit(dexTxType, coinType, assetType, amount, price, callback) { const callbackId = saveCallback(callback) - return send('dexView',{ - dealType:dexTxType, + return send('dexView', { + dealType: dexTxType, amount, - limitPrice:price, - coinType,assetType, + limitPrice: price, + coinType, assetType, callbackId }) }, ///市价交易 dealType : Market_BUY / Market_SELL - DexMarket(dexTxType,coinType,assetType,amount,callback){ + DexMarket(dexTxType, coinType, assetType, amount, callback) { const callbackId = saveCallback(callback) - return send('dexView',{ - dealType:dexTxType, + return send('dexView', { + dealType: dexTxType, amount, - limitPrice:0, - coinType,assetType, + limitPrice: 0, + coinType, assetType, callbackId }) }, - DexCancelOrder(dexTxNum,callback){ + DexCancelOrder(dexTxNum, callback) { const callbackId = saveCallback(callback) - return send('dexCancelView',{ - dealNum:dexTxNum, + return send('dexCancelView', { + dealNum: dexTxNum, callbackId }) }, ///发布资产 - AssetIssue(assetSymbol,assetName,assetSupply,assetOwnerId,assetMintable,callback){ + AssetIssue(assetSymbol, assetName, assetSupply, assetOwnerId, assetMintable, callback) { const callbackId = saveCallback(callback) - return send('AssetPub',{ + return send('AssetPub', { assetSymbol, assetName, assetSupply, @@ -244,40 +244,40 @@ window.WiccWallet = { }) }, - ///资产更新 - AssetUpdate(tokenSymbol,updateType,updateValue,callback){ + ///资产更新 + AssetUpdate(tokenSymbol, updateType, updateValue, callback) { const callbackId = saveCallback(callback) - return send('AssetUpadte',{ - assetSymbol:tokenSymbol, + return send('AssetUpadte', { + assetSymbol: tokenSymbol, updateType, - updateContent:updateValue, + updateContent: updateValue, callbackId }) }, ///请求签名 - SignMessage(imgUrl,appName,message,callback){ + SignMessage(imgUrl, appName, message, callback) { const callbackId = saveCallback(callback) - return send('signMessage',{ - imgUrl,appName,message,callbackId + return send('signMessage', { + imgUrl, appName, message, callbackId }) }, - ///多币种合约调用 - UCoinContractInvoke(amount,coinSymbol,appid,contractMethod,memo,callback,raw){ - const callbackId = saveCallback(callback) - return send('walletPluginUContractInvoke',{ - amount,coinSymbol,regId:appid,contract:contractMethod,memo,callbackId,raw - }) - }, + ///多币种合约调用 + UCoinContractInvoke(amount, coinSymbol, appid, contractMethod, memo, callback, raw) { + const callbackId = saveCallback(callback) + return send('walletPluginUContractInvoke', { + amount, coinSymbol, regId: appid, contract: contractMethod, memo, callbackId, raw + }) + }, - ///多币种转账 - UCoinTransfer(assetMap,memo,callback,raw){ - const callbackId = saveCallback(callback) - return send('UCoinTransfer',{ - assetMap,memo,callbackId,raw - }) - }, + ///多币种转账 + UCoinTransfer(assetMap, memo, callback, raw) { + const callbackId = saveCallback(callback) + return send('UCoinTransfer', { + assetMap, memo, callbackId, raw + }) + }, diff --git a/src/content/inject.js b/src/content/inject.js index 8a14c09..dfd741a 100755 --- a/src/content/inject.js +++ b/src/content/inject.js @@ -4,7 +4,7 @@ script.setAttribute('type', 'text/javascript') script.setAttribute('src', content) document.body.appendChild(script) -const PostMessageStream = require('post-message-stream') +import PostMessageStream from 'post-message-stream' const messageStream = new PostMessageStream({ name: 'contentscript', diff --git a/src/popup/account/components/token-list.vue b/src/popup/account/components/token-list.vue index 34361de..f9a07b8 100755 --- a/src/popup/account/components/token-list.vue +++ b/src/popup/account/components/token-list.vue @@ -47,7 +47,7 @@ @click="handleItemClick({num:myAssets[tokenKey].freeAmount/Math.pow(10,8),name:tokenKey})" >
- + {{tokenKey}}
24){ - return str.substr(0,saveNum)+'...'+str.substring(str.length,str.length-saveNum) + cutMiddleStr(str, saveNum) { + if (str.length > 24) { + return str.substr(0, saveNum) + '...' + str.substring(str.length, str.length - saveNum) } return str }, - formatTime (time) { + formatTime(time) { if (!time) return '' const date = new Date(time) return fecha.format(date, 'YYYY-MM-DD HH:mm:ss') }, - formatAmount (amount, precision = 4) { + formatAmount(amount, precision = 4) { return amount ? fixed(amount * Math.pow(10, -8), precision) : '0' }, - formatFees (fees) { + formatFees(fees) { return fees * Math.pow(10, -8) } } diff --git a/src/popup/account/components/wiccs-history.vue b/src/popup/account/components/wiccs-history.vue index 49af830..f00ecdd 100755 --- a/src/popup/account/components/wiccs-history.vue +++ b/src/popup/account/components/wiccs-history.vue @@ -1,7 +1,13 @@