digibyte
Version:
A pure and powerful JavaScript DigiByte library.
1,463 lines (1,367 loc) • 1.84 MB
JavaScript
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