diff --git a/lib/transaction/payload/commitmenttxpayload.js b/lib/transaction/payload/commitmenttxpayload.js new file mode 100644 index 000000000..d2b1bdd59 --- /dev/null +++ b/lib/transaction/payload/commitmenttxpayload.js @@ -0,0 +1,170 @@ +var utils = require('../../util/js'); +var constants = require('./constants'); +var Preconditions = require('../../util/preconditions'); +var BufferWriter = require('../../encoding/bufferwriter'); +var BufferReader = require('../../encoding/bufferreader'); +var AbstractPayload = require('./abstractpayload'); +var Script = require('../../script'); + +var CURRENT_PAYLOAD_VERSION = 1; + +/** +* @typedef {Object} CommitmentTxPayloadJSON +* @property {number} version uint16_t 2 Version of the final commitment message +* @property {string} quorumHash uint256 32 The quorum identifier +* @property {number} signersSize compactSize uint 1-9 Bit size of the signers bitvector +* @property {string} signers byte[] (bitSize + 7) / 8 Bitset representing the aggregated signers of this final commitment +* @property {number} validMembersSize compactSize uint 1-9 Bit size of the validMembers bitvector +* @property {string} validMembers byte[] (bitSize + 7) / 8 Bitset of valid members in this commitment +* @property {string} quorumPublicKey BLSPubKey 48 The quorum public key +* @property {string} quorumVvecHash uint256 32 The hash of the quorum verification vector +* @property {string} quorumSig BLSSig 96 Recovered threshold signature +* @property {string} sig BLSSig 96 Aggregated BLS signatures from all included commitments +*/ + +/** +* @class CommitmentTxPayload +* @property {number} version +* @property {number} quorumHash +* @property {number} signersSize +* @property {number} signers +* @property {number} validMembersSize +* @property {number} validMembers +* @property {number} quorumPublicKey +* @property {number} quorumVvecHash +* @property {number} quorumSig +* @property {number} sig +*/ + +function CommitmentTxPayload(options) { + AbstractPayload.call(this); + this.version = CURRENT_PAYLOAD_VERSION; + + if (options) { + this.quorumHash = options.quorumHash; + this.signers = options.signers; + this.validMembers = options.validMembers; + this.quorumPublicKey = options.quorumPublicKey; + this.quorumVvecHash = options.quorumVvecHash; + this.quorumSig = options.quorumSig; + this.sig = options.sig; + } +} + +CommitmentTxPayload.prototype = Object.create(AbstractPayload.prototype); +CommitmentTxPayload.prototype.constructor = AbstractPayload; + +/* Static methods */ + +/** + * Parse raw payload + * @param {Buffer} rawPayload + * @return {CommitmentTxPayload} + */ +CommitmentTxPayload.fromBuffer = function fromBuffer(rawPayload) { + var payloadBufferReader = new BufferReader(rawPayload); + var payload = new CommitmentTxPayload(); + payload.version = payloadBufferReader.readUInt16LE(); + payload.quorumHash = payloadBufferReader.read(constants.SHA256_HASH_SIZE).toString('hex'); + + payload.signersSize = payloadBufferReader.readVarintNum(); + payload.signers = payloadBufferReader.read((payload.signersSize + 7) / 8).toString('hex'); + + payload.validMembersSize = payloadBufferReader.readVarintNum(); + payload.validMembers = payloadBufferReader.read((payload.validMembersSize + 7) / 8).toString('hex'); + + payload.quorumPublicKey = payloadBufferReader.read(constants.BLS_PUBLIC_KEY_SIZE).toString('hex'); + payload.quorumVvecHash = payloadBufferReader.read(constants.SHA256_HASH_SIZE).toString('hex'); + payload.quorumSig = payloadBufferReader.read(constants.BLS_SIGNATURE_SIZE).toString('hex'); + payload.sig = payloadBufferReader.read(constants.BLS_SIGNATURE_SIZE).toString('hex'); + + if (!payloadBufferReader.finished()) { + throw new Error('Failed to parse payload: raw payload is bigger than expected.'); + } + + return payload; +}; + +/** + * Create new instance of payload from JSON + * @param {string|CommitmentTxPayloadJSON} payloadJson + * @return {CommitmentTxPayload} + */ +CommitmentTxPayload.fromJSON = function fromJSON(payloadJson) { + var payload = new CommitmentTxPayload(payloadJson); + payload.validate(); + return payload; +}; + +/* Instance methods */ + +/** + * Validate payload + * @return {boolean} + */ +CommitmentTxPayload.prototype.validate = function () { + Preconditions.checkArgument(utils.isUnsignedInteger(this.version), 'Expect version to be an unsigned integer'); + Preconditions.checkArgument(utils.isHexaString(this.quorumHash), 'Expect quorumHash to be a hex string'); + Preconditions.checkArgument(utils.isHexaString(this.signers), 'Expect signers to be a hex string'); + Preconditions.checkArgument(utils.isHexaString(this.validMembers), 'Expect validMembers to be a hex string'); + Preconditions.checkArgument(utils.isHexaString(this.quorumPublicKey), 'Expect quorumPublicKey to be a hex string'); + Preconditions.checkArgument(utils.isHexaString(this.quorumVvecHash), 'Expect quorumVvecHash to be a hex string'); + Preconditions.checkArgument(utils.isHexaString(this.quorumSig), 'Expect quorumSig to be a hex string'); + Preconditions.checkArgument(utils.isHexaString(this.sig), 'Expect sig to be a hex string'); +}; + +/** + * Serializes payload to JSON + * @param [options] + * @return {CommitmentTxPayload} + */ +CommitmentTxPayload.prototype.toJSON = function toJSON(options) { + this.validate(); + var payloadJSON = { + version: this.version, + quorumHash: this.quorumHash, + signerSize: this.signerSize, + signers: this.signers, + validMembersSize: this.validMembersSize, + validMembers: this.validMembers, + quorumPublicKey: this.quorumPublicKey, + quorumVvecHash: this.quorumVvecHash, + quorumSig: this.quorumSig, + sig: this.sig, + }; + + return payloadJSON; +}; + +/** + * Serialize payload to buffer + * @param [options] + * @return {Buffer} + */ +CommitmentTxPayload.prototype.toBuffer = function toBuffer(options) { + this.validate(); + + // var signerSizeLength = Buffer.from(this.signersSize).length; + // var validMemberSizeLength = Buffer.from(this.validMembersSize).length; + + var payloadBufferWriter = new BufferWriter(); + payloadBufferWriter + .writeUInt16LE(this.version) + .write(Buffer.from(this.quorumHash, 'hex')) + .writeVarintNum(Buffer.from(this.signers, 'hex').length * 8 - 7) + .write(Buffer.from(this.signers, 'hex')) + .writeVarintNum(Buffer.from(this.validMembers, 'hex').length * 8 - 7) + .write(Buffer.from(this.validMembers, 'hex')) + .write(Buffer.from(this.quorumPublicKey, 'hex')) + .write(Buffer.from(this.quorumVvecHash, 'hex')) + .write(Buffer.from(this.quorumSig, 'hex')) + .write(Buffer.from(this.sig, 'hex')) + + return payloadBufferWriter.toBuffer(); +}; + +CommitmentTxPayload.prototype.copy = function copy() { + return CommitmentTxPayload.fromBuffer(this.toBuffer()); +}; + +module.exports = CommitmentTxPayload; \ No newline at end of file diff --git a/lib/transaction/payload/constants.js b/lib/transaction/payload/constants.js index 14aaec944..6fd4b4627 100644 --- a/lib/transaction/payload/constants.js +++ b/lib/transaction/payload/constants.js @@ -5,18 +5,22 @@ module.exports = { COMPACT_SIGNATURE_SIZE: 65, // SHA256 hash size in bytes SHA256_HASH_SIZE: 32, + // Quorum BLS Public Key size in bytes + BLS_PUBLIC_KEY_SIZE: 48, + //BLS Signature size in bytes + BLS_SIGNATURE_SIZE: 96, registeredTransactionTypes: { - TRANSACTION_NORMAL : 0, - TRANSACTION_PROVIDER_REGISTER : 1, - TRANSACTION_PROVIDER_UPDATE_SERVICE : 2, - TRANSACTION_PROVIDER_UPDATE_REGISTRAR : 3, - TRANSACTION_PROVIDER_UPDATE_REVOKE : 4, - TRANSACTION_COINBASE : 5, - TRANSACTION_SUBTX_REGISTER : 8, - TRANSACTION_SUBTX_TOPUP : 9, - TRANSACTION_SUBTX_RESETKEY : 10, - TRANSACTION_SUBTX_CLOSEACCOUNT : 11, - TRANSACTION_SUBTX_TRANSITION : 12 + TRANSACTION_NORMAL: 0, + TRANSACTION_PROVIDER_REGISTER: 1, + TRANSACTION_PROVIDER_UPDATE_SERVICE: 2, + TRANSACTION_PROVIDER_UPDATE_REGISTRAR: 3, + TRANSACTION_PROVIDER_UPDATE_REVOKE: 4, + TRANSACTION_COINBASE: 5, + TRANSACTION_SUBTX_REGISTER: 8, + TRANSACTION_SUBTX_TOPUP: 9, + TRANSACTION_SUBTX_RESETKEY: 10, + TRANSACTION_SUBTX_CLOSEACCOUNT: 11, + TRANSACTION_SUBTX_TRANSITION: 12 }, EMPTY_SIGNATURE_SIZE: 0, IP_ADDRESS_SIZE: 16, diff --git a/lib/transaction/payload/index.js b/lib/transaction/payload/index.js index dda116da7..b3cf4d9ae 100644 --- a/lib/transaction/payload/index.js +++ b/lib/transaction/payload/index.js @@ -8,6 +8,7 @@ Payload.SubTxTransitionPayload = require('./subtxtransitionpayload'); Payload.CoinbasePayload = require('./coinbasepayload'); Payload.ProRegTxPayload = require('./proregtxpayload'); Payload.ProTxUpServPayload = require('./proupservtxpayload'); +Payload.CommitmentTxPayload = require('./commitmenttxpayload') Payload.constants = require('./constants'); diff --git a/test/transaction/payload/commitmenttxpayload.js b/test/transaction/payload/commitmenttxpayload.js new file mode 100644 index 000000000..5caab0244 --- /dev/null +++ b/test/transaction/payload/commitmenttxpayload.js @@ -0,0 +1,109 @@ +var expect = require('chai').expect; +var sinon = require('sinon'); + +var DashcoreLib = require('../../../index'); + +var Script = DashcoreLib.Script; +var CommitmentTxPayload = DashcoreLib.Transaction.Payload.CommitmentTxPayload; + +var merkleRootMNList = 'e83c76065797d4542f1cd02e00d02093bea6fb53f5ad6aaa160fd3ccb30001b9'; +console.log(merkleRootMNList); + +var validCommitmentTxPayloadJSON = { + version: 1, + quorumHash: '723d90a45fd882f0df01a56ee1e83f45d03522f390aae5b7f1273745bf2446fd', + signersSize: 9, + signers: 'f01a', + validMembersSize: 9, + validMembers: 'f991', + quorumPublicKey: 'ae5b7f1273724463d90a01a56e45f3d90a01a56e45fd8fd45d03522f390aae5b7f12737244682f0df72e1e83f45bffd5', + quorumVvecHash: '45fd882f0df723d90a01a56ee1e83f45bf2446fd45d03522f390aae5b7f12737', + quorumSig: '3d90a01a56e45fd882f0df72e1e83f45bffd45d03522f390aae5b7f12737241a56e45fd882f0df01a56e45fd882f0df72e1e83f45bffd45d03522f390aae5b7f1273724461a56e45fd882f0df1a56e45fd882f0df1a56e45fd881a56e452f0df', + sig: 'd45d03522f45fd82446f390aae5b7f1273782f0df723d90a01a56ee1e83f45bfd45d03522f45fd82446f390aae5b7f1273782f0df723d90a01a56ee1e83f45bfd45d03522f45fd82446f390aae5b7f1273782f0df723d90a01a56ee1e83f45bf', +}; + +// Todo after commitement tx implemnetation done in core +// var validCommitmentTxPayloadHexString = '' +// var payload = CommitmentTxPayload.fromBuffer(Buffer.from(validCommitmentTxPayloadHexString, 'hex')); + +function checkValidJSON(payload) { + expect(payload.version).to.be.equal(validCommitmentTxPayloadJSON.version); + expect(payload.quorumHash).to.be.equal(validCommitmentTxPayloadJSON.quorumHash); + expect(payload.signers).to.be.equal(validCommitmentTxPayloadJSON.signers); + expect(payload.validMembers).to.be.equal(validCommitmentTxPayloadJSON.validMembers); + expect(payload.quorumPublicKey).to.be.equal(validCommitmentTxPayloadJSON.quorumPublicKey); + expect(payload.quorumVvecHash).to.be.equal(validCommitmentTxPayloadJSON.quorumVvecHash); + expect(payload.quorumSig).to.be.equal(validCommitmentTxPayloadJSON.quorumSig); + expect(payload.sig).to.be.equal(validCommitmentTxPayloadJSON.sig); +} + +describe('CommitmentTxPayload', function () { + + var payload = null; + var payloadBuffer = null; + + describe('.fromJSON', function () { + + beforeEach(function () { + sinon.spy(CommitmentTxPayload.prototype, 'validate'); + }); + + afterEach(function () { + CommitmentTxPayload.prototype.validate.restore(); + }); + + it('Should return instance of CommitmentTxPayload and call #validate on it', function () { + payload = CommitmentTxPayload.fromJSON(validCommitmentTxPayloadJSON); + checkValidJSON(payload); + }); + }); + + describe('.toBuffer', function () { + before(function () { + sinon.spy(CommitmentTxPayload.prototype, 'validate'); + }); + + it('Should return payload buffer of specific length', function () { + + //Manually calculated from validCommitmentTxPayloadJSON + var expectedBufferLength = 312; + + payloadBuffer = payload.toBuffer(); + expect(payloadBuffer.length).to.be.equal(expectedBufferLength); + }); + + after(function () { + CommitmentTxPayload.prototype.validate.restore(); + }) + }); + + describe('.fromBuffer', function () { + before(function () { + sinon.spy(CommitmentTxPayload.prototype, 'validate'); + }); + + it('Should return payload from buffer', function () { + var payloadFromBuffer = CommitmentTxPayload.fromBuffer(payloadBuffer); + checkValidJSON(payloadFromBuffer); + }); + + after(function () { + CommitmentTxPayload.prototype.validate.restore(); + }) + }); + + describe('#toJSON', function () { + beforeEach(function () { + sinon.spy(CommitmentTxPayload.prototype, 'validate'); + }); + + afterEach(function () { + CommitmentTxPayload.prototype.validate.restore(); + }); + + it('Should be able to serialize payload JSON', function () { + var payloadJSON = payload.toJSON(); + checkValidJSON(payloadJSON); + }); + }); +}); \ No newline at end of file