UNPKG

parsec-lib

Version:

transaction and block implementation

631 lines (473 loc) 29.3 kB
'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}(); /** * Copyright (c) 2018-present, Parsec Labs (parseclabs.org) * * This source code is licensed under the GNU Affero General Public License, * version 3, found in the LICENSE file in the root directory of this source * tree. */ var _ethereumjsUtil = require('ethereumjs-util');var _ethereumjsUtil2 = _interopRequireDefault(_ethereumjsUtil); var _fastEquals = require('fast-equals'); var _input = require('./input');var _input2 = _interopRequireDefault(_input); var _output = require('./output');var _output2 = _interopRequireDefault(_output); var _outpoint = require('./outpoint');var _outpoint2 = _interopRequireDefault(_outpoint); var _util = require('./util'); var _type = require('./type');var _type2 = _interopRequireDefault(_type);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}} var EMPTY_BUF = Buffer.alloc(32, 0);var Transaction = function () { function Transaction(type) {var inputs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];var outputs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];var options = arguments[3];_classCallCheck(this, Transaction); this.type = type; this.inputs = inputs; this.outputs = outputs; this.options = options; // recover signer if we have signatures without signer if (this.inputs.length && this.inputs[0].v && !this.inputs[0].signer) { this.recoverTxSigner(); } }_createClass(Transaction, [{ key: 'getSize', /* * Returns raw transaction size. * See `toRaw` for details. */value: function getSize() { if (this.type === _type2.default.DEPOSIT) { return _output.OUT_LENGTH + 6; } if (this.type === _type2.default.TRANSFER) { return 2 + this.inputs.reduce(function (s, i) {return s + i.getSize();}, 0) + this.outputs.reduce(function (s, o) {return s + o.getSize();}, 0); } return this.toRaw().length; } // Recovers signer address for each of the inputs // Requires inputs to be signed. }, { key: 'recoverTxSigner', value: function recoverTxSigner() {var _this = this; this.inputs.map(function (i) {return i.recoverSigner(_this.sigDataBuf());}); } // Returns sigData as Buffer. // Calculated as follows: // 1. serialize to bytes // 2. strip out input signatures }, { key: 'sigDataBuf', value: function sigDataBuf() { return Transaction.sigDataBufStatic(this.type, this.toRaw(), this.inputs.length); } // Returns sigData as hex string }, { key: 'sigData', value: function sigData() { return _ethereumjsUtil2.default.bufferToHex(this.sigDataBuf()); } // Signs each input with provided private keys // @param {Array} privKeys - array of private keys strings. }, { key: 'sign', value: function sign(privKeys) { if (this.type === _type2.default.PERIOD_VOTE) { var sig = _ethereumjsUtil2.default.ecsign( this.inputs[0].prevout.hash, // Buffer.from(this.options.merkleRoot.replace('0x', ''), 'hex'), Buffer.from(privKeys[0].replace('0x', ''), 'hex')); this.inputs[0].setSig( sig.r, sig.s, sig.v, // sig _ethereumjsUtil2.default.bufferToHex(_ethereumjsUtil2.default.privateToAddress(privKeys[0])) // signer ); return this; } if (privKeys.length !== this.inputs.length) { throw Error('amount of private keys doesn\'t match amount of inputs'); } if (this.type === _type2.default.CONSOLIDATE) { // no signatures needed return this; } var startIdx = this.type === _type2.default.TRANSFER ? 0 : 1; for (var i = startIdx; i < privKeys.length; i++) { var sigHashBuf = _ethereumjsUtil2.default.hashPersonalMessage(this.sigDataBuf()); var _sig = _ethereumjsUtil2.default.ecsign( sigHashBuf, Buffer.from(privKeys[i].replace('0x', ''), 'hex')); this.inputs[i].setSig( _sig.r, _sig.s, _sig.v, // sig _ethereumjsUtil2.default.bufferToHex(_ethereumjsUtil2.default.privateToAddress(privKeys[i])) // signer ); } return this; } }, { key: 'signAll', value: function signAll( privKey) { return this.sign(this.inputs.map(function () {return privKey;})); } }, { key: 'getConditionSig', value: function getConditionSig( privKey) { if (this.type !== _type2.default.SPEND_COND) { throw Error('invalid Tx type: ' + this.type); } // spend_cond sighash is calculated with 00 for msgData // as message data will contain the signature for spending the tx var bytesStripped = this.inputs. filter(function (i) {return i.msgData;}). map(function (i) {return i.msgData.length;}). reduce(function (a, b) {return a + b;}, 0); var raw = this.toRaw(); var noSigs = Buffer.alloc(raw.length - bytesStripped, 0); var readOffset = 0; var writeOffset = 0; // copy type and lengths raw.copy(noSigs, writeOffset, readOffset, 2); readOffset += 2; writeOffset += 2; for (var i = 0; i < this.inputs.length; i += 1) { raw.copy(noSigs, writeOffset, readOffset, readOffset + 37); var msgLength = raw.readUInt16BE(readOffset + 37); readOffset += 37 + 2 + msgLength; writeOffset += 37 + 2; var scriptLength = raw.readUInt16BE(readOffset); raw.copy(noSigs, writeOffset, readOffset, readOffset + 2 + scriptLength); readOffset += 2 + scriptLength; writeOffset += 2 + scriptLength; } raw.copy(noSigs, writeOffset, readOffset, raw.length); var sigHash = Buffer.alloc(32, 0); _ethereumjsUtil2.default.ripemd160(noSigs).copy(sigHash, 12, 0, 20); return _ethereumjsUtil2.default.ecsign( sigHash, Buffer.from(privKey.replace('0x', ''), 'hex')); } }, { key: 'sigHash', value: function sigHash() { return _ethereumjsUtil2.default.bufferToHex(this.sigHashBuf()); } }, { key: 'sigHashBuf', value: function sigHashBuf() { if (this.type === _type2.default.SPEND_COND) { // spend_cond sighash is calculated with 00 for msgData // as message data will contain the signature for spending the tx var bytesStripped = this.inputs. filter(function (i) {return i.msgData;}). map(function (i) {return i.msgData.length;}). reduce(function (a, b) {return a + b;}, 0); var raw = this.toRaw(); var noSigs = Buffer.alloc(raw.length - bytesStripped, 0); var readOffset = 0; var writeOffset = 0; // copy type and lengths raw.copy(noSigs, writeOffset, readOffset, 2); readOffset += 2; writeOffset += 2; for (var i = 0; i < this.inputs.length; i += 1) { raw.copy(noSigs, writeOffset, readOffset, readOffset + 37); var msgLength = raw.readUInt16BE(readOffset + 37); readOffset += 37 + 2 + msgLength; writeOffset += 37 + 2; var scriptLength = raw.readUInt16BE(readOffset); raw.copy(noSigs, writeOffset, readOffset, readOffset + 2 + scriptLength); readOffset += 2 + scriptLength; writeOffset += 2 + scriptLength; } raw.copy(noSigs, writeOffset, readOffset, raw.length); return _ethereumjsUtil2.default.ripemd160(noSigs); } return _ethereumjsUtil2.default.keccak256(this.sigDataBuf()); } }, { key: 'signWeb3', value: function signWeb3( web3) {var _this2 = this;var accountIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (this.type === _type2.default.CONSOLIDATE) { // no signatures needed return this; } if (this.type === _type2.default.PERIOD_VOTE) { return Transaction.signMessageWithWeb3(web3, this.inputs[0].prevout.hash, accountIndex). then(function (_ref) {var r = _ref.r,s = _ref.s,v = _ref.v,signer = _ref.signer; _this2.inputs[0].setSig(r, s, v, signer); return _this2; }); } return Transaction.signMessageWithWeb3(web3, this.sigData(), accountIndex). then(function (_ref2) {var r = _ref2.r,s = _ref2.s,v = _ref2.v,signer = _ref2.signer; var inputs = _this2.type === _type2.default.TRANSFER ? _this2.inputs : _this2.inputs.slice(1); inputs.forEach(function (input) { input.setSig(r, s, v, signer); }); return _this2; }); } // Returns tx hash as Buffer }, { key: 'hashBuf', value: function hashBuf() { var raw = this.toRaw(); if (this.type === _type2.default.TRANSFER && raw.slice(34, 66).equals(EMPTY_BUF)) { throw Error('not signed yet'); } return _ethereumjsUtil2.default.keccak256(raw); } // Returns tx hash as hex string }, { key: 'hash', value: function hash() { return _ethereumjsUtil2.default.bufferToHex(this.hashBuf()); } // Returns serialized tx bytes as hex string }, { key: 'hex', value: function hex() { return _ethereumjsUtil2.default.bufferToHex(this.toRaw()); } // Checks if this tx is equal to `another` }, { key: 'equals', value: function equals(another) { return (0, _fastEquals.deepEqual)(this, another); } /** * * Serialized this tx in bytes as follows: * * * Deposit (60 bytes): * * 0 - 1 byte type * 1 - 4 bits number of inputs (always 1) * 1 - 4 bits number of outputs (always 1) * 2 - 4 bytes depositId * 6 - 32 bytes value * 38 - 2 bytes color * 40 - 20 bytes address * 60 * * Transfer (314 bytes): * * 0 - 1 byte type * 1 - 4 bits number of inputs * 1 - 4 bits number of outputs * 2 - 32 bytes prev tx $ * 34 - 1 byte output pos $ first input * 35 - 32 bytes r-signature # * 67 - 32 bytes s-signature # first signature * 99 - 1 byte v-signature # * 108 - 32 bytes prev tx $ * 140 - 1 byte output pos $ second input * 141 - 32 bytes r-signature # * 173 - 32 bytes s-signature # second signature * 205 - 1 byte v-signature # * 206 - 32 bytes value $ * 238 - 2 bytes color $ * 240 - 20 bytes address $ first output * 260 - 32 bytes value # * 292 - 2 bytes color # * 294 - 20 bytes address # second output * 314 * * Consolidate (167 bytes): * * 0 - 1 byte type * 1 - 1 byte number of inputs * 2 - 32 bytes prev tx # * 34 - 1 byte output pos # first input * 35 - 32 bytes r-signature # * 67 - 32 bytes s-signature # first signature * 99 - 1 byte v-signature # * 100 - 32 bytes prev tx # * 132 - 1 byte output pos # second input * 133 - 32 bytes r-signature # * 165 - 32 bytes s-signature # second signature * 166 - 1 byte v-signature # * 167 * * */ }, { key: 'toRaw', value: function toRaw() { var payload = void 0; var inputs = []; if (this.type === _type2.default.DEPOSIT) { payload = Buffer.alloc(6, 0); payload.writeUInt8(this.type, 0); payload.writeUInt8(16 + 1, 1); // 1 inputs, 1 output payload.writeUInt32BE(this.options.depositId, 2); } else if (this.type === _type2.default.EPOCH_LENGTH) { payload = Buffer.alloc(6, 0); payload.writeUInt8(this.type, 0); payload.writeUInt8(0, 1); // 0 inputs, 0 output payload.writeUInt32BE(this.options.epochLength, 2); } else if (this.type === _type2.default.VALIDATOR_JOIN) { payload = Buffer.alloc(60, 0); payload.writeUInt8(this.type, 0); payload.writeUInt8(0, 1); // 0 inputs, 0 output payload.writeUInt16BE(this.options.slotId, 2); payload.write(this.options.tenderKey.replace('0x', ''), 4, 'hex'); payload.writeUInt32BE(this.options.eventsCount, 36); payload.write(this.options.signerAddr.replace('0x', ''), 40, 'hex'); } else if (this.type === _type2.default.VALIDATOR_LOGOUT) { payload = Buffer.alloc(64, 0); payload.writeUInt8(this.type, 0); payload.writeUInt8(0, 1); // 0 inputs, 0 output payload.writeUInt16BE(this.options.slotId, 2); payload.write(this.options.tenderKey.replace('0x', ''), 4, 'hex'); payload.writeUInt32BE(this.options.eventsCount, 36); payload.writeUInt32BE(this.options.activationEpoch, 40); payload.write(this.options.newSigner.replace('0x', ''), 44, 'hex'); } else if (this.type === _type2.default.PERIOD_VOTE) { payload = Buffer.alloc(3, 0); payload.writeUInt8(this.type, 0); payload.writeUInt8(16, 1); // 1 input, 0 output payload.writeUInt8(this.options.slotId, 2); inputs = this.inputs; } else if (this.type === _type2.default.EXIT) { payload = Buffer.alloc(34, 0); payload.writeUInt8(this.type, 0); (0, _util.arrayToRaw)(this.inputs).copy(payload, 1, 0, 33); } else if (this.type === _type2.default.TRANSFER) { payload = Buffer.alloc(2, 0); payload.writeUInt8(this.type, 0); // write ins and outs length as nibbles payload.writeUInt8(16 * this.inputs.length + this.outputs.length, 1); inputs = this.inputs; } else if (this.type === _type2.default.CONSOLIDATE) { payload = Buffer.alloc(2, 0); payload.writeUInt8(this.type, 0); // always one output, no need to read second nibble payload.writeUInt8(16 * this.inputs.length + 1, 1); inputs = this.inputs; } else if (this.type === _type2.default.SPEND_COND) { payload = Buffer.alloc(2, 0); payload.writeUInt8(this.type, 0); // write ins and outs length as nibbles payload.writeUInt8(16 * this.inputs.length + this.outputs.length, 1); inputs = this.inputs; } else { payload = Buffer.alloc(1, 0); payload.writeUInt8(this.type, 0); inputs = this.inputs; } return Buffer.concat([payload, (0, _util.arrayToRaw)(inputs), (0, _util.arrayToRaw)(this.outputs)]); } }, { key: 'toJSON', value: function toJSON() { var json = { type: this.type, hash: this.hash(), inputs: this.inputs.map(function (inp) {return inp.toJSON();}), outputs: this.outputs.map(function (out) {return out.toJSON();}) }; if (this.options) { json.options = this.options; } return json; } }], [{ key: 'epochLength', value: function epochLength(_epochLength) {return new Transaction(_type2.default.EPOCH_LENGTH, [], [], { epochLength: _epochLength });} }, { key: 'validatorJoin', value: function validatorJoin(slotId, tenderKey, eventsCount, signerAddr) {return new Transaction(_type2.default.VALIDATOR_JOIN, [], [], { slotId: slotId, eventsCount: eventsCount, signerAddr: signerAddr.toLowerCase(), tenderKey: tenderKey.toLowerCase() });} }, { key: 'validatorLogout', value: function validatorLogout(slotId, tenderKey, eventsCount, activationEpoch, newSigner) {return new Transaction(_type2.default.VALIDATOR_LOGOUT, [], [], { slotId: slotId, eventsCount: eventsCount, tenderKey: tenderKey.toLowerCase(), activationEpoch: activationEpoch, newSigner: newSigner.toLowerCase() });} }, { key: 'periodVote', value: function periodVote(slotId, input) {return new Transaction(_type2.default.PERIOD_VOTE, [input], [], { slotId: slotId });} }, { key: 'deposit', value: function deposit(depositId, value, address, color) {return new Transaction(_type2.default.DEPOSIT, [], [new _output2.default(value, address, color)], { depositId: depositId });} }, { key: 'exit', value: function exit(input) {return new Transaction(_type2.default.EXIT, [input]);} }, { key: 'transfer', value: function transfer(inputs, outputs) {return new Transaction(_type2.default.TRANSFER, inputs, outputs);} }, { key: 'consolidate', value: function consolidate(inputs, output) {inputs.forEach(function (input) {input.type = _type2.default.CONSOLIDATE; // eslint-disable-line });return new Transaction(_type2.default.CONSOLIDATE, inputs, [output]);} }, { key: 'spendCond', value: function spendCond(inputs, outputs) {inputs[0].type = _type2.default.SPEND_COND; // eslint-disable-line no-param-reassign return new Transaction(_type2.default.SPEND_COND, inputs, outputs);} }, { key: 'sigDataBufStatic', value: function sigDataBufStatic(type, raw, inputsLength) {var noSigs = Buffer.alloc(raw.length, 0);var offset = 2; // copy type and lengths raw.copy(noSigs, 0, 0, offset);for (var i = 0; i < inputsLength; i += 1) {raw.copy(noSigs, offset, offset, offset + 33);offset += type !== _type2.default.TRANSFER && type !== _type2.default.CONSOLIDATE && i === 0 ? 33 : 98;}raw.copy(noSigs, offset, offset, raw.length);return noSigs;} }, { key: 'signMessageWithWeb3', value: function signMessageWithWeb3(web3, message) {var accountIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;var version = typeof web3.version === 'string' ? web3.version : web3.version.api;return new Promise(function (resolve, reject) {web3.eth.getAccounts(function (err, accounts) {if (err) {return reject('getAccounts err: ' + err);}if (!accounts[accountIndex]) {return reject(new Error('No account at index ' + accountIndex));}var args = version.startsWith('0') ? [accounts[accountIndex], message] : [message, accounts[accountIndex]];args.push(function (sigErr, sig) {if (sigErr) {return reject(sigErr);}var _ethUtil$fromRpcSig = _ethereumjsUtil2.default.fromRpcSig(sig),r = _ethUtil$fromRpcSig.r,s = _ethUtil$fromRpcSig.s,v = _ethUtil$fromRpcSig.v;return resolve({ r: r, s: s, v: v, signer: accounts[accountIndex] });});var _ref3 = web3.currentProvider.isMetaMask ? web3.eth.personal : web3.eth,sign = _ref3.sign;return sign.apply(undefined, args);});});} }, { key: 'fromJSON', value: function fromJSON(_ref4) {var type = _ref4.type,inputs = _ref4.inputs,outputs = _ref4.outputs,options = _ref4.options;return new Transaction( type, inputs.map(_input2.default.fromJSON), outputs.map(_output2.default.fromJSON), options); } }, { key: 'parseToParams', value: function parseToParams( transaction) { var bufs = this.parseToBuf(transaction); return bufs.parts.map(function (buf) {return '0x' + buf.toString('hex');}); } // Constructs Transaction from given raw bytes // @returns {Transaction} }, { key: 'fromRaw', value: function fromRaw(transaction) { var dataBuf = transaction; if (!Buffer.isBuffer(transaction)) { var dataHex = transaction.replace('0x', ''); dataBuf = Buffer.alloc(dataHex.length / 2); dataBuf.write(dataHex, 'hex'); } var type = dataBuf.readUInt8(0); switch (type) { case _type2.default.DEPOSIT:{ if (dataBuf.length !== _output.OUT_LENGTH + 6) { throw new Error('malformed deposit tx.'); } var depositId = dataBuf.readUInt32BE(2); var output = _output2.default.fromRaw(dataBuf.slice(6)); return new Transaction(_type2.default.DEPOSIT, [], [output], { depositId: depositId }); } case _type2.default.EPOCH_LENGTH:{ if (dataBuf.length !== 6) { throw new Error('malformed epoch tx.'); } var epochLength = dataBuf.readUInt32BE(2); return Transaction.epochLength(epochLength); } case _type2.default.VALIDATOR_JOIN:{ if (dataBuf.length !== 60) { throw new Error('malformed validatorJoin tx.'); } var slotId = dataBuf.readUInt16BE(2); var tenderKey = '0x' + dataBuf.slice(4, 36).toString('hex'); var eventsCount = dataBuf.readUInt32BE(36); var signerAddr = '0x' + dataBuf.slice(40, 60).toString('hex'); return Transaction.validatorJoin(slotId, tenderKey, eventsCount, signerAddr); } case _type2.default.VALIDATOR_LOGOUT:{ if (dataBuf.length !== 64) { throw new Error('malformed validatorLogout tx.'); } var _slotId = dataBuf.readUInt16BE(2); var _tenderKey = '0x' + dataBuf.slice(4, 36).toString('hex'); var _eventsCount = dataBuf.readUInt32BE(36); var activationEpoch = dataBuf.readUInt32BE(40); var newSigner = '0x' + dataBuf.slice(44, 64).toString('hex'); return Transaction.validatorLogout( _slotId, _tenderKey, _eventsCount, activationEpoch, newSigner); } case _type2.default.PERIOD_VOTE:{ if (dataBuf.length !== 101) { throw new Error('malformed periodVote tx.'); } var _slotId2 = dataBuf.readUInt8(2); var ins = []; var sigHashBuf = dataBuf.slice(3, 35); ins.push(_input2.default.fromRaw(dataBuf, 3, sigHashBuf)); return new Transaction(_type2.default.PERIOD_VOTE, ins, [], { slotId: _slotId2 }); } case _type2.default.EXIT:{ if (dataBuf.length !== 34) { throw new Error('malformed exit tx.'); } var outpoint = _outpoint2.default.fromRaw(dataBuf, 1); return new Transaction(_type2.default.EXIT, [new _input2.default(outpoint)], []); } case _type2.default.TRANSFER:{ var insOuts = dataBuf.readUInt8(1); var insLength = insOuts >> 4; // eslint-disable-line no-bitwise var outsLength = insOuts & 0xF; // eslint-disable-line no-bitwise var _ins = []; var _sigHashBuf = _ethereumjsUtil2.default.hashPersonalMessage( Transaction.sigDataBufStatic(type, dataBuf, insLength)); for (var i = 0; i < insLength; i += 1) { _ins.push(_input2.default.fromRaw(dataBuf, 2 + i * _input.SPEND_INPUT_LENGTH, _sigHashBuf)); } var outs = []; for (var _i = 0; _i < outsLength; _i += 1) { outs.push(_output2.default.fromRaw( dataBuf, 2 + insLength * _input.SPEND_INPUT_LENGTH + _i * _output.OUT_LENGTH)); } return new Transaction(_type2.default.TRANSFER, _ins, outs); } case _type2.default.SPEND_COND:{ var _insOuts = dataBuf.readUInt8(1); var _insLength = _insOuts >> 4; // eslint-disable-line no-bitwise var _outsLength = _insOuts & 0xF; // eslint-disable-line no-bitwise var _ins2 = []; var offset = 2; for (var _i2 = 0; _i2 < _insLength; _i2 += 1) { var input = _input2.default.fromRaw(dataBuf, offset, _type2.default.SPEND_COND); _ins2.push(input); offset += 33 + 4 + 2 + 2 + input.msgData.length + input.script.length; } var _outs = []; for (var _i3 = 0; _i3 < _outsLength; _i3 += 1) { _outs.push(_output2.default.fromRaw( dataBuf, offset + _i3 * _output.OUT_LENGTH)); } return new Transaction(_type2.default.SPEND_COND, _ins2, _outs); } case _type2.default.CONSOLIDATE:{ var _insOuts2 = dataBuf.readUInt8(1); var _insLength2 = _insOuts2 >> 4; // eslint-disable-line no-bitwise var _ins3 = []; var unsignedInputLength = 33; for (var _i4 = 0; _i4 < _insLength2; _i4 += 1) { _ins3.push(_input2.default.fromRaw(dataBuf, 2 + _i4 * unsignedInputLength, _type2.default.CONSOLIDATE)); } var _outs2 = []; _outs2.push(_output2.default.fromRaw(dataBuf, 2 + _insLength2 * unsignedInputLength)); return new Transaction(_type2.default.CONSOLIDATE, _ins3, _outs2); } default:{ throw new Error('unknown transaction type: ' + type + '.'); }} } }]);return Transaction;}();exports.default = Transaction;module.exports = exports['default'];