UNPKG

digibyte

Version:

A pure and powerful JavaScript DigiByte library.

1,463 lines (1,367 loc) 1.84 MB
require=(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ (function (Buffer){ 'use strict'; var _ = require('lodash'); var $ = require('./util/preconditions'); var errors = require('./errors'); var Bech32 = require('./encoding/bech32'); var Bech32Check = require('./encoding/bech32check'); var Base58Check = require('./encoding/base58check'); var Networks = require('./networks'); var Hash = require('./crypto/hash'); var JSUtil = require('./util/js'); var PublicKey = require('./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, legacy) { /* 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, legacy); } 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 && type !== Address.PayToWitnessPublicKeyHash && type !== Address.PayToWitnessScriptHash)) { throw new TypeError('Third argument must be "pubkeyhash", "scripthash", "witnesspubkeyhash", or "witnessscripthash".'); } 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 || data.length === 34)) { 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'; /** @static */ Address.PayToWitnessPublicKeyHash = 'paytowitnesspublickeyhash'; /** @static */ Address.PayToWitnessScriptHash = 'paytowitnessscripthash'; /** @static */ Address.PayToPublicKey = 'publickey'; /** * @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 ? new Buffer(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, network, prefix) { var version = {}; var pubkeyhashNetwork = Networks.get(buffer[0], 'pubkeyhash'); var scripthashNetwork = Networks.get(buffer[0], 'scripthash'); var scripthashTwoNetwork = Networks.get(buffer[0], 'scripthashTwo'); if (prefix) { if (buffer.length === 20) { version.type = Address.PayToWitnessPublicKeyHash; } else if (buffer.length === 32) { version.type = Address.PayToWitnessScriptHash; } else { throw new TypeError('Invalid buffer length for segwit address') } version.network = Networks.get(prefix, 'prefix');; } else { if (pubkeyhashNetwork) { version.network = pubkeyhashNetwork; version.type = Address.PayToPublicKeyHash; } else if (scripthashNetwork || scripthashTwoNetwork) { version.network = scripthashNetwork || scripthashTwoNetwork; 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, prefix) { /* jshint maxcomplexity: 9 */ var info = {}; if (!(buffer instanceof Buffer) && !(buffer instanceof Uint8Array)) { throw new TypeError('Address supplied is not a buffer.'); } if (prefix && buffer.length !== 20 && buffer.length !== 32) { throw new TypeError('Address buffer is incorrect length.'); } else if (!prefix && buffer.length !== 1 + 20) { throw new TypeError('Address buffer is incorrect length.'); } var networkObj = Networks.get(network); var bufferVersion = Address._classifyFromVersion(buffer, network, prefix); 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 = prefix ? buffer : 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, legacy) { network = network || publicKeys[0].network || Networks.defaultNetwork; var redeemScript = Script.buildMultisigOut(publicKeys, threshold); if (!legacy) { var hash = Hash.sha256(redeemScript.toBuffer()); var words = Bech32.toWords(hash); words.unshift(0); var info = {}; info.hashBuffer = Buffer.from(words); info.type = Address.PayToWitnessScriptHash; info.network = network; return new Address(Bech32Check.encode(Buffer.from(words), network.prefix), 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.'); } if (data.length < 34){ throw new Error('Invalid Address string provided'); } data = data.trim(); try { var result = Bech32.decode(data); var version = result.shift(); var buf = Bech32.fromWords(result); network = Networks.get(data.split('1')[0], 'prefix'); var info = Address._transformBuffer(Buffer.from(buf), network, type, network.prefix); return info; } catch (e) { if (type === Address.PayToWitnessPublicKeyHash || type === Address.PayToWitnessScriptHash) { return e; } } 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, legacy) { $.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 = new Buffer(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; }; /** * Returns true if an address is of pay to witness pubkeyhash type * @return boolean */ Address.prototype.isPayToWitnessPublicKeyHash = function() { return this.type === Address.PayToWitnessPublicKeyHash; }; /** * Returns true if an address is of pay to witness script hash type * @return boolean */ Address.prototype.isPayToWitnessScriptHash = function() { return this.type === Address.PayToWitnessScriptHash; }; /** * Will return a buffer representation of the address * * @returns {Buffer} Bitcoin address buffer */ Address.prototype.toBuffer = function() { var version = new Buffer([this.network[this.type]]); var buf = Buffer.concat([version, this.hashBuffer]); return buf; }; /** * Instantiate a bech32 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.toBech32Address = function(data, network) { return new Address({ hashBuffer: Hash.sha256ripemd160(data), type: Address.PayToWitnessPublicKeyHash, network: network || this.network }); }; /** * @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() { if (this.type === Address.PayToWitnessPublicKeyHash || this.type === Address.PayToWitnessScriptHash) { var words = Bech32.toWords(this.hashBuffer); words.unshift(0); return Bech32Check.encode(Buffer.from(words), this.network.prefix); } return Base58Check.encode(this.toBuffer()); }; /** * Will return a string formatted for the console * * @returns {string} Bitcoin address */ Address.prototype.inspect = function() { return '<Address: ' + this.toString() + ', type: ' + this.type + ', network: ' + this.network + '>'; }; /*** * Computes a checksum from the given input data as specified for the CashAddr * format: https://github.com/Bitcoin-UAHF/spec/blob/master/cashaddr.md. * * @param {Array} data Array of 5-bit integers over which the checksum is to be computed. */ var GENERATOR1 = [0x98, 0x79, 0xf3, 0xae, 0x1e]; var GENERATOR2 = [0xf2bc8e61, 0xb76d99e2, 0x3e5fb3c4, 0x2eabe2a8, 0x4f43e470]; function polymod(data) { // Treat c as 8 bits + 32 bits var c0 = 0, c1 = 1, C = 0; for (var j = 0; j < data.length; j++) { // Set C to c shifted by 35 C = c0 >>> 3; // 0x[07]ffffffff c0 &= 0x07; // Shift as a whole number c0 <<= 5; c0 |= c1 >>> 27; // 0xffffffff >>> 5 c1 &= 0x07ffffff; c1 <<= 5; // xor the last 5 bits c1 ^= data[j]; for (var i = 0; i < GENERATOR1.length; ++i) { if (C & (1 << i)) { c0 ^= GENERATOR1[i]; c1 ^= GENERATOR2[i]; } } } c1 ^= 1; // Negative numbers -> large positive numbers if (c1 < 0) { c1 ^= 1 << 31; c1 += (1 << 30) * 2; } // Unless bitwise operations are used, // numbers are consisting of 52 bits, except // the sign bit. The result is max 40 bits, // so it fits perfectly in one number! return c0 * (1 << 30) * 4 + c1; } module.exports = Address; var Script = require('./script'); }).call(this,require("buffer").Buffer) },{"./crypto/hash":15,"./encoding/base58check":20,"./encoding/bech32":21,"./encoding/bech32check":22,"./errors":26,"./networks":30,"./publickey":33,"./script":34,"./util/js":56,"./util/preconditions":57,"buffer":116,"lodash":174}],2:[function(require,module,exports){ (function (Buffer){ 'use strict'; var _ = require('lodash'); var rsa = require('node-rsa'); var $ = require('../util/preconditions'); var assetUtils = require('../util/assets'); var Base58Check = require('../encoding/base58check'); var bn = require('../crypto/bn'); var BufferUtil = require('../util/buffer'); var Hash = require('../crypto/hash'); var Address = require('../address'); var OpCode = require('../opcode'); var Script = require('../script'); var Transaction = require('../transaction'); var Unit = require('../unit'); var POSTFIXBYTELENGTH = 2; var UNLOCKEPADDING = { aggregatable: 0x2e37, hybrid: 0x2e6b, dispersed: 0x2e4e } var LOCKEPADDING = { aggregatable: 0x20ce, hybrid: 0x2102, dispersed: 0x20e4 } var OP_CODES = { 'issuance': { 'start': 0x00, 'end': 0x0f, 'encoder': require('./issuanceencoder') }, 'transfer': { 'start': 0x10, 'end': 0x1f, 'encoder': require('./transferencoder') }, 'burn': { 'start': 0x20, 'end': 0x2f, 'encoder': require('./transferencoder') } } var encodingLookup = {} for (var transactionType in OP_CODES) { for (var j = OP_CODES[transactionType].start; j <= OP_CODES[transactionType].end; j++) { encodingLookup[j] = {}; encodingLookup[j].encoder = OP_CODES[transactionType].encoder; encodingLookup[j].type = transactionType; } } var padLeadingZeros = function (hex, byteSize) { if (!byteSize) { byteSize = Math.ceil(hex.length / 2) } return (hex.length === byteSize * 2) ? hex : padLeadingZeros('0' + hex, byteSize) } var paymentsInputToSkip = function (payments) { var result = JSON.parse(JSON.stringify(payments)) result.sort(function (a, b) { return a.input - b.input }) for (var i = 0; i < result.length; i++) { var skip = false if (result[i + 1] && result[i + 1].input > result[i].input) { skip = true } delete result[i].input result[i].skip = skip } return result } var paymentsSkipToInput = function (payments) { var paymentsDecoded = [] var input = 0 for (var i = 0; i < payments.length; i++) { var paymentDecoded = payments[i].burn ? {burn: true} : {range: payments[i].range, output: payments[i].output} paymentDecoded.input = input paymentDecoded.percent = payments[i].percent paymentDecoded.amount = payments[i].amount paymentsDecoded.push(paymentDecoded) if (payments[i].skip) input = input + 1 } return paymentsDecoded } /** * Represents a digiasset, * * @constructor * @param {object} data * @param {string} data.type digiasset encoding type * @param {string} data.noRules * @param {array} data.payments any payments including in the transaction * @param {string} data.protocol the asset protocol * @param {string} data.version digiasset transaction version * @param {string} data.lockStatus is the data locked * @param {string} data.aggregationPolicy asset aggregation policy * @param {number} data.divisibility asset divisibility * @param {array} data.multiSig any associated multisig addresses * @param {number} data.amount the amount being transfered * @param {string} data.sha2 the sha2 hash of the torrent if included * @param {string} data.torrentHash trrent hash */ function Asset(data) { /* jshint maxcomplexity: 20 */ /* jshint maxstatements: 20 */ if (!(this instanceof Asset)) { return new Asset(data); } if(data) { this.aggregationPolicy = data.aggregationPolicy || 'aggregatable'; this.assetId = data.assetId || ''; this.type = data.type || undefined; this.lockStatus = data.lockStatus || true; this.multiSig = data.multiSig || []; this.payments = data.payments || []; this.amount = data.amount; this.issueAddress = data.issueAddress; this.to = data.to; this.from = data.from; this.burn = data.burn; this.protocol = data.protocol || Asset.ASSET_IDENTIFIER; this.version = data.version || Asset.DA_TX_VERSION; this.divisibility = data.divisibility || 0; this.urls = data.urls || []; this.transfer = data.transfer || []; this.metadata = data.metadata; this.rules = data.rules || []; this.falgs = data.flags; this.fee = data.fee; this.financeOutput = data.financeOutput; this.financeOutputTxid = data.financeOutputTxid; this.sha1 = data.sha1; this.sha2 = data.sha2; this.ignoreMetadata = data.ignoreMetadata || false; } if(!this.type) { this.fromBuffer(data); } } Asset.MIN_FEE = 1000; Asset.DA_TX_VERSION = 0x02; Asset.ASSET_IDENTIFIER = 0x4441; Asset.MAXBYTESIZE = 80; /** * Converts asset from OP_Return buffer * * @param {Object} data * @return {Asset} */ Asset.prototype.fromBuffer = function(data) { var decoder = encodingLookup[data[3]]; var rawData = new decoder.encoder().decode(data); this.protocol = rawData.protocol; this.version = rawData.version; this.multiSig = rawData.multiSig || []; this.payments = paymentsSkipToInput(rawData.payments); this.type = decoder.type; if (this.type === 'issuance') { this.lockStatus = rawData.lockStatus this.aggregationPolicy = rawData.aggregationPolicy this.divisibility = rawData.divisibility this.amount = rawData.amount } return this; }; /** * Sets the asset amount * * @param {Number} amount * @param {Number} divisibility */ Asset.prototype.setAmount = function(amount, divisibility) { $.checkState(amount , 'Amount must be set'); this.type = 'issuance'; this.divisibility = divisibility || 0; this.amount = amount; } /** * Sets the asset lock status * * @param {Boolean} lockStatus */ Asset.prototype.setLockStatus = function(lockStatus) { this.lockStatus = lockStatus; this.type = 'issuance'; } /** * Sets the asset aggregation policy * * @param {String} aggregationPolicy */ Asset.prototype.setAggregationPolicy = function(aggregationPolicy) { this.aggregationPolicy = aggregationPolicy || 'aggregatable'; this.type = 'issuance'; } /** * Sets the asset torrent hash * * @param {String} torrentHash * @param {String} sha2 */ Asset.prototype.setHash = function(torrentHash, sha2) { if (!torrentHash) throw new Error('Can\'t set hashes without the torrent hash'); if (!Buffer.isBuffer(torrentHash)) { torrentHash = new Buffer(torrentHash, 'hex'); } this.torrentHash = torrentHash; if (sha2) { if (!Buffer.isBuffer(sha2)) { sha2 = new Buffer(sha2, 'hex'); } this.sha2 = sha2; } } /** * Sets the asset noRules var * */ Asset.prototype.allowRules = function() { this.noRules = false; } /** * Gets the AssetID * * @param {Input} firstInput */ Asset.prototype.getAssetId = function(firstInput) { var script = firstInput.script; var firstInputObj = firstInput.toObject(); var padding; if (this.lockStatus) { padding = LOCKEPADDING[this.aggregationPolicy]; return this.createIdFromTxidIndex(firstInputObj, padding); } padding = UNLOCKEPADDING[this.aggregationPolicy]; if (firstInputObj.previousOutput && firstInputObj.previousOutput.hex) { return createIdFromPreviousOutputScriptPubKey(firstInputObj.previousOutput.hex, padding, divisibility); } return this.createIdFromPubKeyHashInput(script, padding); }; /** * Creates AssetID from txid and input index * * @param {Input} firstInput * @param {Number} padding */ Asset.prototype.createIdFromTxidIndex = function(firstInput, padding) { var str = firstInput.prevTxId + ':' + firstInput.outputIndex; this.assetId = this.hashAndBase58CheckEncode(Buffer.from(str), padding); return this.assetId; }; /** * Creates AssetId from pubkey hash * * @param {Script} script * @param {Number} padding */ Asset.prototype.createIdFromPubKeyHashInput = function(script, padding) { var Script = require('../script'); var pubKeyHash = new Address(Hash.sha256ripemd160(script.chunks[1].buf)); var pubKeyHashOutput = Script.buildPublicKeyHashOut(pubKeyHash).toBuffer(); this.assetId = this.hashAndBase58CheckEncode(pubKeyHashOutput, padding); return this.assetId; }; /** * Hash and base58 encode the assetID * * @param {String} payloadToHash * @param {Number} padding */ Asset.prototype.hashAndBase58CheckEncode = function(payloadToHash, padding) { var hash256 = Hash.sha256(payloadToHash); var hash160 = Hash.ripemd160(hash256); padding = new Buffer(padLeadingZeros(padding.toString(16)), 'hex'); var divisibility = new Buffer(padLeadingZeros(this.divisibility.toString(16), POSTFIXBYTELENGTH), 'hex'); var concatenation = Buffer.concat([padding, hash160, divisibility]); return Base58Check.encode(concatenation);; }; /** * Encode the asset * */ Asset.prototype.encode = function() { var encoder = OP_CODES[this.type]; this.payments = paymentsInputToSkip(this.payments); var result = new encoder.encoder(this).encode(Asset.MAXBYTESIZE); this.payments = paymentsSkipToInput(this.payments); return result; }; /** * Adds a payment to the asset * * @param {Input} input * @param {Number} amount * @param {Number} output * @param {Number} range * @param {Number} percent */ Asset.prototype.addPayment = function(input, amount, output, range, percent) { var range = range || false; var percent = percent || false; this.payments.push({input: input, amount: amount, output: output, range: range, percent: percent}); }; /** * Adds a burn payment to the asset * * @param {Input} input * @param {Number} amount * @param {Number} percent */ Asset.prototype.addBurn = function (input, amount, percent) { if (this.type === 'issuance') { throw new Error('Can\'t add burn payment to an issuance transaction') } this.payments.push({ input: input, amount: amount, percent: percent, burn: true }); this.type = 'burn'; } /** * Encrypts the asset data * * @param {Object} assetData */ Asset.prototype.tryEncryptData = function (assetData) { try { if(assetData.metadata && assetData.metadata.encryptions && assetData.metadata.userData) { var oneKey = new rsa({b: 1024}) var returnKey = false assetData.metadata.encryptions.forEach(function (encSection){ returnKey = returnKey || !encSection.pubKey var section = assetData.metadata.userData[encSection.key] if(section) { var format = encSection.type + '-public-' + encSection.format; var key = encSection.pubKey ? new rsa([encSection.pubKey]) : oneKey; var encrypted = key.encrypt(section, 'base64'); assetData.metadata.userData[encSection.key] = encrypted; } }); return { privateKey: returnKey ? oneKey.exportKey('pkcs8').toString('hex') : '' }; } } catch (e) { return e; } } /** * Gets the metadata * */ Asset.prototype.getMetaData = function() { var metafile = {}; if(this.metadata) { var key = this.tryEncryptData(); if (key && key.error) { throw new Error('Encryption error: ' + key.error); } else if (key && key.privateKey) { this.privateKey = key.privateKey; } metafile.data = this.metadata; if(this.rules) { metafile.rules = this.rules; } } return metafile; } /** * Finds the best matching utxos containg assets * * @param {Array} utxos * @param {Array} assetList * @param {String} key * @param {Transaction} tx * @param {Object} inputvalues * @param {Object} medata * @return {String} fee */ Asset.prototype.findBestMatchByNeededAssets = function(utxos, assetList, key, tx, inputvalues) { var self = this; var selectedUtxos = []; var foundAmount = 0; var bestGreaterOrEqualAmountUtxo = this.findBestGreaterOrEqualAmountUtxo(utxos, assetList, key); if (bestGreaterOrEqualAmountUtxo) { selectedUtxos[0] = bestGreaterOrEqualAmountUtxo; } else { var utxosSortedByAssetAmount = _.sortBy(utxos, function (utxo) { return -self.getUtxoAssetAmount(utxo, key) }); var found = utxosSortedByAssetAmount.some(function (utxo) { selectedUtxos.push(utxo); foundAmount += self.getUtxoAssetAmount(utxo, key); return foundAmount >= assetList[key].amount; }); if (!found) { selectedUtxos.length = 0; } } if (!selectedUtxos.length) { return false; } var lastAssetId; selectedUtxos.some(function (utxo) { utxo.assets.forEach(function (asset) { try { var overflow = true; if (assetList[asset.assetId] && !assetList[asset.assetId].done) { var inputIndex = tx.inputs.length if (!tx.inputs.some(function (txutxo, i) { if (txutxo.index === utxo.index && BufferUtil.reverse(txutxo.hash).toString('hex') === utxo.txid) { inputIndex = i; return true } return false })) { var output = new Transaction.UnspentOutput({ address: utxo.address, txid: utxo.txid, vout: utxo.index, scriptPubKey: utxo.scriptPubKey.hex, amount: utxo.value, }); tx.from(output); inputvalues.amount += Math.round(utxo.value); if (self.flags && self.flags.injectPreviousOutput) { tx.inputs[tx.inputs.length - 1].script = Script.fromHex(utxo.scriptPubKey.hex) } } var aggregationPolicy = asset.aggregationPolicy || 'aggregatable'; // TODO - remove after all assets have this field var inputIndexInAsset = assetList[asset.assetId].inputs.length; if (assetList[asset.assetId].amount <= asset.amount) { var totalamount = asset.amount; if (aggregationPolicy === 'aggregatable' && lastAssetId === asset.assetId && assetList[asset.assetId].inputs.length) { assetList[asset.assetId].inputs[inputIndexInAsset - 1].amount += assetList[asset.assetId].amount; } else { assetList[asset.assetId].inputs.push({index: inputIndex, amount: assetList[asset.assetId].amount}); } assetList[asset.assetId].change = totalamount - assetList[asset.assetId].amount; assetList[asset.assetId].done = true; } else { if (aggregationPolicy === 'aggregatable' && lastAssetId === asset.assetId && assetList[asset.assetId].inputs.length) { assetList[asset.assetId].inputs[inputIndexInAsset - 1].amount += asset.amount; } else { assetList[asset.assetId].inputs.push({index: inputIndex, amount: asset.amount}); } assetList[asset.assetId].amount -= asset.amount; } } } catch (e) { throw e; } lastAssetId = asset.assetId; }); return assetList[key].done; }); return true; } /** * Finds the best utxo matching a key * * @param {Array} utxos * @param {Array} assetList * @param {String} key * @return {Boolean} */ Asset.prototype.findBestGreaterOrEqualAmountUtxo = function (utxos, assetList, key) { var foundLargerOrEqualAmountUtxo = false; var self = this; utxos.forEach(function (utxo) { utxo.score = 0; var assetAmount = self.getUtxoAssetAmount(utxo, key); if (assetAmount < assetList[key].amount) { return; } foundLargerOrEqualAmountUtxo = true; if (assetAmount === assetList[key].amount) { utxo.score += 10000; } else { // assetAmount > assetList[key].amount utxo.score += 1000; } for (var assetId in assetList) { if (assetId === key) continue; assetAmount = self.getUtxoAssetAmount(utxo, assetId); if (assetAmount === assetList[assetId].amount) { utxo.score += 100; } else if (assetAmount > assetList[assetId].amount) { utxo.score += 10; } else { // assetAmount < assetList[assetId].amount utxo.score += assetAmount / assetList[assetId].amount; } } }); return foundLargerOrEqualAmountUtxo && _.maxBy(utxos, function (utxo) { return utxo.score }); } /** * Inserts digitoshi into the current transaction * * @param {Array} utxos * @param {Transaction} tx * @param {Number} missing * @param {Number} inputsValue */ Asset.prototype.insertSatoshiToTransaction = function(utxos, tx, missing, inputsValue) { var self = this; var paymentDone = false; var missingbn = new bn(missing); var financeValue = new bn(0); var currentAmount = new bn(0); if(self.financeOutput && self.financeOutputTxid) { if(self.isInputInTx(tx, self.financeOutputTxid, self.financeOutput.n)) { return false; } financeValue = new bn(self.financeOutput.value); if(financeValue.minus(missingbn) >= 0) { //TODO: check there is no asset here tx.addInput( self.financeOutputTxid, self.financeOutput.n); inputsValue.amount += financeValue.toNumber() ; if( self.flags && self.flags.injectPreviousOutput) { tx.inputs[tx.inputs.length -1].script = Script.fromHex(self.financeOutput.scriptPubKey.hex); } paymentDone = true; return paymentDone; } } else { var hasEnoughEquity = utxos.some(function (utxo) { utxo.value = Math.round(utxo.value) if (!self.isInputInTx(tx, utxo.txid, utxo.index) && !(utxo.assets && utxo.assets.length)) { var output = new Transaction.UnspentOutput({ address: utxo.address, txid: utxo.txid, vout: utxo.index, scriptPubKey: utxo.scriptPubKey.hex, amount: Unit.fromSatoshis(utxo.value).toDGB(), }); tx.from(output); inputsValue.amount += utxo.value; currentAmount = currentAmount.add(new bn(utxo.value)); if(self.flags && self.flags.injectPreviousOutput) { tx.inputs[tx.inputs.length -1].script = Script.fromHex(utxo.scriptPubKey.hex); } } return currentAmount.cmp(missingbn) >= 0; }); return hasEnoughEquity; } } /** * Gets the value from a utxo * * @param {UnspentOutput} utxo * @param {String} assetId */ Asset.prototype.getUtxoAssetAmount = function(utxo, assetId) { return _(utxo.assets).filter(function (asset) { return asset.assetId === assetId }).sumBy('amount'); } /** * Adds enough inputs to fulfull the fee requirement * * @param {Transaction} tx * @param {Array} utxos * @param {Number} totalInputs * @param {Number} satoshiCost */ Asset.prototype.tryAddingInputsForFee = function(tx, utxos, totalInputs, satoshiCost) { if(satoshiCost > totalInputs.amount) { if(!this.insertSatoshiToTransaction(utxos, tx, (satoshiCost - totalInputs.amount), totalInputs)) { return false; } } return true; } /** * Is this input already in the tx * * @param {Transaction} tx * @param {String} txid * @param {Number} index */ Asset.prototype.isInputInTx = function(tx, txid, index) { return tx.inputs.some(function (input) { var id = BufferUtil.reverse(input.prevTxId); return (id.toString('hex') === txid && input.index === index); }); } /** * Gets the total Cost of the issuance transactions * * @param {Object} metaobj * @param {Boolean} withFee * @return {Number} fee */ Asset.prototype.getTotalIssuenceCost = function(withFee) { var fee = withFee ? Asset.MIN_FEE : 0; if(this.transfer && this.transfer.length) { this.transfer.forEach(function(to) { fee += Transaction.DUST_AMOUNT; }); } if(this.rules || this.metadata) { fee += 700; // MULTISIG_MIN_DUST } fee += Transaction.DUST_AMOUNT; return fee; } /** * Gets Issuance cost * */ Asset.prototype.getIssuenceCost = function() { return this.getTotalIssuenceCost(true); } /** * Computes the cost of the transaction * * @param {Boolean} withFee */ Asset.prototype.comupteCost = function(withFee) { var fee = withFee ? (this.fee || Asset.MIN_FEE) : 0; if(this.to && this.to.length) { this.to.forEach(function(to) { fee += Transaction.DUST_AMOUNT; }); } if(this.rules || this.metadata) { fee += 700; } fee += Transaction.DUST_AMOUNT; return fee; } /** * Gets the transaction change * * @param {Transaction} tx * @param {Number} totalInputValue */ Asset.prototype.getChangeAmount = function(tx, totalInputValue) { var allOutputValues = _.sumBy(tx.outputs, function(output) { return output.toObject().satoshis; }); return (totalInputValue.amount - (allOutputValues + this.fee)); } Asset.prototype.getNoneMinDustByScript = function(script, useFee) { varfee = useFee || Transaction.FEE_PER_KB; // add 9 to aacount for bitcoind SER_DISK serilaztion before the multiplication return (((Transaction.FEE_PER_KB * (script.toBuffer().length + 148 + 9 )) / 1000) * 3); } Asset.prototype.getInputAmountNeededForTx = function(tx, fee) { var total = fee || Transaction.FEE_PER_KB; tx.outputs.forEach(function(output){ total += this.getNoneMinDustByScript(output.script, fee); }); return total; } /** * Adds inputs to the asset transfer transaction. * * @param {Transaction} tx * @param {Object} assetData * @param {Array} utxos * @return {Object} fee */ Asset.prototype.addInputsForSendTransaction = function(tx, utxos) { var self = this; var assetList = []; var totalInputs = { amount: 0 }; var satoshiCost = this.comupteCost(true); var coloredOutputIndexes = []; var reedemScripts = []; self.to.forEach(function(to) { if(!assetList[to.assetId]) { assetList[to.assetId] = { amount: 0, addresses: [], done: false, change: 0, encodeAmount: 0, inputs: [] }; } assetList[to.assetId].amount += to.amount; if (to.burn) { assetList[to.assetId].addresses.push({ address: 'burn', amount: to.amount }); } else if (!to.address && to.pubKeys && to.m) { // ToDo var multisig = generateMultisigAddress(to.pubKeys, to.m) assetList[to.assetId].addresses.push({ address: multisig.address, amount: to.amount, reedemScript: multisig.reedemScript}) } else { assetList[to.assetId].addresses.push({ address: to.address, amount: to.amount}); } }); for( var asset in assetList) { var assetUtxos = utxos.filter(function (element, index, array) { if (!element.assets) { return false; } return element.assets.some(function(a){ return (a.assetId == asset); }); }); if(assetUtxos && assetUtxos.length > 0) { var key = asset; assetUtxos.forEach(function (utxo){ if(utxo.used) { throw new Error('Output Alreaedy Spent - output: ' + utxo.txid + ':' + utxo.index); } }); if(!self.findBestMatchByNeededAssets(assetUtxos, assetList, key, tx, totalInputs)) { throw new Error('Not enough assets - asset: ' + key); } } else { throw new Error('No output with that asset - asset: ' + asset); } } if(!self.tryAddingInputsForFee(tx, utxos, totalInputs, satoshiCost)) { throw new Error('Not enough funds'); } for( asset in assetList) { var currentAsset = assetList[asset]; if(!currentAsset.done) { return new Error('Not enough Assets - asset: ' + asset); } var uniAssets = _.uniqBy(currentAsset.addresses, function(item) { return item.address; } ); uniAssets.forEach(function(address) { var addressAmountLeft = address.amount; currentAsset.inputs.some(function (input) { if(!input.amount) { return false; } if(addressAmountLeft - input.amount > 0 ) { if (address.address === 'burn') { self.addBurn(input.index, input.amount); } else { self.addPayment(input.index, input.amount, (tx.outputs ? tx.outputs.length : 0)); } addressAmountLeft -= input.amount; input.amount = 0; return false; } else { if (address.address === 'burn') { self.addBurn(input.index, addressAmountLeft); } else { self.addPayment(input.index, addressAmountLeft, (tx.outputs ? tx.outputs.length : 0)); } input.amount -= addressAmountLeft; addressAmountLeft = 0; return true; } }); if (address.address !== 'burn') { tx.to(address.address, Transaction.DUST_AMOUNT); } if(address.reedemScript) { reedemScripts.push({index: tx.outputs.length -1, reedemScript: address.reedemScript, address: address.address}); } }); } try { //add metadata if we have any if((self.metadata || self.rules) && !self.ignoreMetadata) { if(!self.sha1 || !self.sha2) { throw new Error('Missing Torrenthash!'); } self.setHash(self.sha1, self.sha2); } var buffer = self.encode(); if(buffer.leftover && buffer.leftover.length > 0) { self.shiftOutputs(); reedemScripts.forEach(function(item) { item.index += 1 }); buffer = self.encode(); if(buffer.leftover.length == 1) { //To Do addHashesOutput(tx, self.pubKeyReturnMultisigDust, buffer.leftover[0]); } else if(buffer.leftover.length == 2) { addHashesOutput(tx, self.pubKeyReturnMultisigDust, buffer.leftover[1], buffer.leftover[0]); } else { throw new Error('have hashes and enough room we offested inputs for nothing'); } } // add array of colored ouput indexes self.payments.forEach(function (payment) { if (typeof payment.output !== 'undefined') { coloredOutputIndexes.push(payment.output); } }); } catch(e) { throw e; } tx.addData(buffer.codeBuffer); var lastOutputValue = self.getChangeAmount(tx, totalInputs); var coloredChange = _.keys(assetList).some(function (assetId) { return assetList[assetId].change > 0; }); var numOfChanges = (self.flags && self.flags.splitChange && coloredChange && lastOutputValue >= 2 * Transaction.DUST_AMOUNT) ? 2 : 1; if(lastOutputValue < numOfChanges * Transaction.DUST_AMOUNT) { satoshiCost = self.getInputAmountNeededForTx(tx, self.fee) + numOfChanges * Transaction.DUST_AMOUNT; if(!self.tryAddingInputsForFee(tx, utxos, totalInputs, satoshiCost)) { throw new Error('Not Enough funds'); } lastOutputValue = self.getChangeAmount(tx, totalInputs); } // TODO: make sure we have a from here, even though we try to use f