diff --git a/modules/ecash-script/README.md b/modules/ecash-script/README.md --- a/modules/ecash-script/README.md +++ b/modules/ecash-script/README.md @@ -23,7 +23,7 @@ ``` consumeNextPush({remainingHex:'042e786563'}) -// 2e786563 +// {data: 2e786563, pushedWith: '04'} ``` `swapEndianness` @@ -40,3 +40,4 @@ 1.0.0 Initial support for OP_RETURN parsing with functions `consume` and `consumeNextPush` 1.1.0 New functions `swapEndianness` and `isHexString` +2.0.0 Modify `consumeNextPush` to return object `{data, pushedWith}` instead of string `data` diff --git a/modules/ecash-script/package.json b/modules/ecash-script/package.json --- a/modules/ecash-script/package.json +++ b/modules/ecash-script/package.json @@ -1,6 +1,6 @@ { "name": "ecash-script", - "version": "1.1.0", + "version": "2.0.0", "description": "Parse script for eCash (XEC)", "main": "index.js", "scripts": { diff --git a/modules/ecash-script/src/script.js b/modules/ecash-script/src/script.js --- a/modules/ecash-script/src/script.js +++ b/modules/ecash-script/src/script.js @@ -61,7 +61,7 @@ * If the stack does not start with a valid push, it raises an error and the stack is left untouched. * @param {object} stack an object containing a hex string outputScript of an eCash tx, e.g. {remainingHex: '4d...'} * An object is used for 'stack' to allow in-place modification without returning the input - * @returns {string} the first push on input stack, as a hex string + * @returns {object} {data, pushedWith} * stack is modified in place so that the push is removed */ function consumeNextPush(stack) { @@ -69,59 +69,54 @@ const clonedStack = JSON.parse(JSON.stringify(stack)); // Get the first byte on the stack - let firstByte = module.exports.consume(clonedStack, 1); + let pushOpCode = consume(clonedStack, 1); - if (opReturn.oneByteStackAdds.includes(firstByte)) { + if (opReturn.oneByteStackAdds.includes(pushOpCode)) { // If this is a one-byte push, consume stack and return the byte stack.remainingHex = clonedStack.remainingHex; - return firstByte; + return { data: pushOpCode, pushedWith: pushOpCode }; } - // Initialize pushdata, the byteCount in hex of the push - let pushdata; + // Initialize variables + let pushBytecountHex, data; // Apply conditional checks to determine the size of this push - if (opReturn.oneBytePushdatas.includes(firstByte)) { - // If the first byte on the stack is 0x01-0x4b, then this is pushdata - pushdata = parseInt(firstByte, 16); - } else if (firstByte === opReturn.OP_PUSHDATA1) { + if (opReturn.oneBytePushdatas.includes(pushOpCode)) { + // If the first byte on the stack is 0x01-0x4b, then this is pushedBytesHex + pushBytecountHex = pushOpCode; + } else if (pushOpCode === opReturn.OP_PUSHDATA1) { // The next byte contains the number of bytes to be pushed onto the stack. - pushdata = parseInt(module.exports.consume(clonedStack, 1), 16); - } else if (firstByte === opReturn.OP_PUSHDATA2) { + pushBytecountHex = consume(clonedStack, 1); + } else if (pushOpCode === opReturn.OP_PUSHDATA2) { // The next two bytes contain the number of bytes to be pushed onto the stack in little endian order. - - // pushdata is the the next 2 bytes, as little-endian - // i.e. 04 would be 0x04 0x00 i.e. '0400' - pushdata = module.exports.consume(clonedStack, 2); - - // Convert to big-endian so JS parseInt can get the value - pushdata = swapEndianness(pushdata); - - // Convert to int - pushdata = parseInt(pushdata, 16); - } else if (firstByte === opReturn.OP_PUSHDATA4) { + pushBytecountHex = consume(clonedStack, 2); + } else if (pushOpCode === opReturn.OP_PUSHDATA4) { // The next four bytes contain the number of bytes to be pushed onto the stack in little endian order. - - // pushdata is the first byte of the next 4 bytes, as little-endian - // i.e. 04 would be 0x04 0x00 0x00 0x00 i.e. '04000000' - pushdata = module.exports.consume(clonedStack, 4); - - // Convert to big-endian so JS parseInt can get the value - pushdata = swapEndianness(pushdata); - - // Convert to int - pushdata = parseInt(pushdata, 16); + pushBytecountHex = consume(clonedStack, 4); } else { - throw new Error(`${firstByte} is invalid pushdata`); + throw new Error(`${pushOpCode} is invalid pushdata`); } - // Now that you know pushdata in bytes, get the push - let push = module.exports.consume(clonedStack, pushdata); + // Now that you know how many bytes are in the push, get the pushed data + data = consume(clonedStack, parseInt(swapEndianness(pushBytecountHex), 16)); // If no error, consume stack stack.remainingHex = clonedStack.remainingHex; - return push; + /* + Return {data, pushedWith} + Note that if the first byte on the stack is 0x01-0x4b, + this is both pushOpCode and pushBytecountHex + + You don't want to return '0404' for e.g. '042e786563' + Conditionally remove pushBytecountHex for this case + */ + return { + data, + pushedWith: `${pushOpCode}${ + pushOpCode !== pushBytecountHex ? pushBytecountHex : '' + }`, + }; } module.exports = { diff --git a/modules/ecash-script/test/script.js b/modules/ecash-script/test/script.js --- a/modules/ecash-script/test/script.js +++ b/modules/ecash-script/test/script.js @@ -131,7 +131,7 @@ '6a042e78656300154361706974616c4c6574746572735f416e645f2b21150076458db0ed96fe9863fc1ccec9fa2cfab884b0f6', ); }); - it('consumeNextPush parses a valid alias registration OP_RETURN stack for all valid pushdata types', function () { + it('consumeNextPush returns expected pushdata for an OP_RETURN stack', function () { const expectedPushes = [ '2e786563', // .xec '00', // version 0 @@ -170,28 +170,43 @@ remainingHex: `${thesePushdatas[0]}${expectedPushes[0]}${thesePushdatas[1]}${expectedPushes[1]}${thesePushdatas[2]}${expectedPushes[2]}${thesePushdatas[3]}${expectedPushes[3]}`, }; // First push, prefix - assert.strictEqual(consumeNextPush(testStack), expectedPushes[0]); + assert.deepEqual(consumeNextPush(testStack), { + data: expectedPushes[0], + pushedWith: thesePushdatas[0], + }); // Stack is modified so that push is removed assert.deepEqual(testStack, { remainingHex: `${thesePushdatas[1]}${expectedPushes[1]}${thesePushdatas[2]}${expectedPushes[2]}${thesePushdatas[3]}${expectedPushes[3]}`, }); // Second push, alias registration tx version number - assert.strictEqual(consumeNextPush(testStack), expectedPushes[1]); + assert.deepEqual(consumeNextPush(testStack), { + data: expectedPushes[1], + pushedWith: + thesePushdatas[1] === '' + ? expectedPushes[1] + : thesePushdatas[1], + }); // Stack is modified so that push is removed assert.deepEqual(testStack, { remainingHex: `${thesePushdatas[2]}${expectedPushes[2]}${thesePushdatas[3]}${expectedPushes[3]}`, }); // Third push, alias - assert.strictEqual(consumeNextPush(testStack), expectedPushes[2]); + assert.deepEqual(consumeNextPush(testStack), { + data: expectedPushes[2], + pushedWith: thesePushdatas[2], + }); // Stack is modified so that push is removed assert.deepEqual(testStack, { remainingHex: `${thesePushdatas[3]}${expectedPushes[3]}`, }); // Fourth push, <addressType><addressHash> - assert.strictEqual(consumeNextPush(testStack), expectedPushes[3]); + assert.deepEqual(consumeNextPush(testStack), { + data: expectedPushes[3], + pushedWith: thesePushdatas[3], + }); // Stack is modified so that push is removed assert.deepEqual(testStack, { remainingHex: '', @@ -246,7 +261,7 @@ '4e040404042e78656300154361706974616c4c6574746572735f416e645f2b21150076458db0ed96fe9863fc1ccec9fa2cfab884b0f6', }); }); - it('consumeNextPush returns all one byte pushes', function () { + it('consumeNextPush returns all one byte pushes and empty string for associated pushdata', function () { for (let i = 0; i < opReturn.oneByteStackAdds.length; i += 1) { const testHexString = opReturn.oneByteStackAdds[i]; const stack = { remainingHex: testHexString }; @@ -254,7 +269,10 @@ const firstPush = consumeNextPush(stack); // alias registration prefix // consumeNextPush returns the push of testHexString - assert.strictEqual(firstPush, opReturn.oneByteStackAdds[i]); + assert.deepEqual(firstPush, { + data: opReturn.oneByteStackAdds[i], + pushedWith: opReturn.oneByteStackAdds[i], + }); // Verify stack has been modified in place assert.deepEqual(stack, { remainingHex: '',