diff --git a/functions/encode-script-chunks.js b/functions/encode-script-chunks.js new file mode 100644 index 0000000..beb253d --- /dev/null +++ b/functions/encode-script-chunks.js @@ -0,0 +1,23 @@ +const encodeHex = require('./encode-hex') +const opcodes = require('../constants/opcodes') +const Script = require('../classes/script') + +const OPCODE_MAP = [] + +Object.entries(opcodes).forEach(([value, key]) => { OPCODE_MAP[key] = value }) + +function encodeScriptChunks (chunks) { + const asm = chunks.map(chunk => { + if (chunk.buf) { + return encodeHex(chunk.buf) || '0' + } else if (chunk.opcode === opcodes.OP_1NEGATE) { + return '-1' + } else { + return OPCODE_MAP[chunk.opcode] || `` + } + }).join(' ') + + return Script.fromASM(asm) +} + +module.exports = encodeScriptChunks diff --git a/functions/index.js b/functions/index.js index 5b3ad1b..0398694 100644 --- a/functions/index.js +++ b/functions/index.js @@ -30,6 +30,7 @@ module.exports = { encodeHex: require('./encode-hex'), encodePublicKey: require('./encode-public-key'), encodePushData: require('./encode-push-data'), + encodeScriptChunks: require('./encode-script-chunks'), encodeTx: require('./encode-tx'), encodeWIF: require('./encode-wif'), evalScript: require('./eval-script'), @@ -59,6 +60,7 @@ module.exports = { sha256ripemd160: require('./sha256ripemd160'), sighashAsync: require('./sighash-async'), sighash: require('./sighash'), + subscript: require('./subscript'), verifyPoint: require('./verify-point'), verifyPrivateKey: require('./verify-private-key'), verifyScriptAsync: require('./verify-script-async'), diff --git a/functions/subscript.js b/functions/subscript.js new file mode 100644 index 0000000..9ea3a6e --- /dev/null +++ b/functions/subscript.js @@ -0,0 +1,30 @@ +const decodeScriptChunks = require('./decode-script-chunks') +const encodeScriptChunks = require('./encode-script-chunks') + +const OP_CODESEPARATOR = 171 + +function subscript (script, n) { + if (!Number.isInteger(parseInt(n)) || n < 0) throw new Error('invalid number') + + try { + const scriptChunks = decodeScriptChunks(script) + + let index = 0; + + for (let i = 0; i < scriptChunks.length; ++i) { + if (scriptChunks[i].opcode === OP_CODESEPARATOR) { + if (index === n){ + return encodeScriptChunks(scriptChunks.slice(i + 1)) + } else { + ++index; + } + } + } + } catch (err) { + throw new Error(`couldn't get the subscript: ${err}`) + } + + return script +} + +module.exports = subscript diff --git a/test/functions/encode-script-chunks.js b/test/functions/encode-script-chunks.js new file mode 100644 index 0000000..4f20458 --- /dev/null +++ b/test/functions/encode-script-chunks.js @@ -0,0 +1,28 @@ +const { describe, it } = require('mocha') +const { expect } = require('chai') +const nimble = require('../env/nimble') +const { Script } = nimble.classes +const { encodeScriptChunks, decodeScriptChunks } = nimble.functions + +describe('verify encodeScriptChunks', () => { + it('valid chunk encoding', () => { + const asm1 = 'OP_1 OP_2' + const asm2 = '' + const script1 = Script.fromASM(asm1) + const script2 = Script.fromASM(asm2) + const chunks1 = decodeScriptChunks(script1) + const chunks2 = decodeScriptChunks(script2) + expect(encodeScriptChunks(chunks1)).to.deep.equal(script1) + expect(encodeScriptChunks(chunks2)).to.deep.equal(script2) + }) + + it('bad chunks', () => { + const chunks1 = 'wrong type' + const chunks2 = [ + { opcode: 256 } + ] + + expect(() => encodeScriptChunks(chunks1)).to.throw('chunks.map is not a function') + expect(() => encodeScriptChunks(chunks2)).to.throw('bad hex char') + }) +}) diff --git a/test/functions/subscript.js b/test/functions/subscript.js new file mode 100644 index 0000000..4991892 --- /dev/null +++ b/test/functions/subscript.js @@ -0,0 +1,34 @@ +const { describe, it } = require('mocha') +const { expect } = require('chai') +const nimble = require('../env/nimble') +const { Script } = nimble.classes +const { subscript } = nimble.functions + +describe('verify subscript', () => { + it('valid subscripts', () => { + const asm1 = '' + const asm2 = 'OP_1 OP_2' + const asm3 = '0 OP_1 OP_CODESEPARATOR OP_4 OP_5 OP_CODESEPARATOR OP_6 OP_CODESEPARATOR' + const asm3_0 = 'OP_4 OP_5 OP_CODESEPARATOR OP_6 OP_CODESEPARATOR' + const asm3_2 = '' + const script1 = Script.fromASM(asm1) + const script2 = Script.fromASM(asm2) + const script3 = Script.fromASM(asm3) + const script3_0 = Script.fromASM(asm3_0) + const script3_2 = Script.fromASM(asm3_2) + expect(subscript(script1, 99)).to.deep.equal(script1) + expect(subscript(script2, 99)).to.deep.equal(script2) + expect(subscript(script3, 99)).to.deep.equal(script3) + expect(subscript(script3, 0)).to.deep.equal(script3_0) + expect(subscript(script3, 2)).to.deep.equal(script3_2) + }) + + it('bad values', () => { + const script = new Script() + const noScript = nimble.PrivateKey.fromRandom() + + expect(() => subscript(script)).to.throw('invalid number') + expect(() => subscript(script, -1)).to.throw('invalid number') + expect(() => subscript(noScript, 0)).to.throw('bad script') + }) +}) diff --git a/test/index.js b/test/index.js index 79014ce..a17217f 100644 --- a/test/index.js +++ b/test/index.js @@ -37,6 +37,7 @@ describe('functions', () => { require('./functions/encode-hex') require('./functions/encode-public-key') require('./functions/encode-push-data') + require('./functions/encode-script-chunks') require('./functions/encode-tx') require('./functions/encode-wif') require('./functions/eval-script') @@ -59,6 +60,7 @@ describe('functions', () => { require('./functions/sha256d') require('./functions/sighash-async') require('./functions/sighash') + require('./functions/subscript') require('./functions/verify-point') require('./functions/verify-private-key') require('./functions/verify-tx-signature')