diff --git a/src/templates/index.js b/src/templates/index.js index 78a8eba..fa7c4ea 100644 --- a/src/templates/index.js +++ b/src/templates/index.js @@ -6,6 +6,8 @@ var pubKeyHash = require('./pubkeyhash') var scriptHash = require('./scripthash') var witnessPubKeyHash = require('./witnesspubkeyhash') var witnessScriptHash = require('./witnessscripthash') +var witnessCommitment = require('./witnesscommitment') + var types = { MULTISIG: 'multisig', NONSTANDARD: 'nonstandard', @@ -14,7 +16,8 @@ var types = { P2PKH: 'pubkeyhash', P2SH: 'scripthash', P2WPKH: 'witnesspubkeyhash', - P2WSH: 'witnessscripthash' + P2WSH: 'witnessscripthash', + WITNESS_COMMITMENT: 'witnesscommitment' } function classifyOutput (script) { @@ -27,6 +30,7 @@ function classifyOutput (script) { var chunks = decompile(script) if (multisig.output.check(chunks)) return types.MULTISIG if (pubKey.output.check(chunks)) return types.P2PK + if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT if (nullData.output.check(chunks)) return types.NULLDATA return types.NONSTANDARD @@ -65,5 +69,6 @@ module.exports = { scriptHash: scriptHash, witnessPubKeyHash: witnessPubKeyHash, witnessScriptHash: witnessScriptHash, + witnessCommitment: witnessCommitment, types: types } diff --git a/src/templates/witnesscommitment/index.js b/src/templates/witnesscommitment/index.js new file mode 100644 index 0000000..d459038 --- /dev/null +++ b/src/templates/witnesscommitment/index.js @@ -0,0 +1,3 @@ +module.exports = { + output: require('./output') +} diff --git a/src/templates/witnesscommitment/output.js b/src/templates/witnesscommitment/output.js new file mode 100644 index 0000000..e07f401 --- /dev/null +++ b/src/templates/witnesscommitment/output.js @@ -0,0 +1,36 @@ +// OP_RETURN 36bytes:[0xaa21a9ed, Hash256(witnessRoot )] + +var bscript = require('../../script') +var types = require('../../types') +var typeforce = require('typeforce') +var OPS = require('bitcoin-ops') + +function check (script) { + var buffer = bscript.compile(script) + + return buffer.length > 37 && + buffer[0] === OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).toString('hex') === 'aa21a9ed' +} + +check.toJSON = function () { return 'Witness commitment output' } + +function encode (commitment) { + // hash256 0x21 hash160 0xed + typeforce(types.Hash256bit, commitment) + + return bscript.compile([OPS.OP_RETURN, new Buffer('aa21a9ed' + commitment.toString('hex'), 'hex')]) +} + +function decode (buffer) { + typeforce(check, buffer) + + return bscript.decompile(buffer)[1].slice(4, 36) +} + +module.exports = { + check: check, + decode: decode, + encode: encode +} diff --git a/test/fixtures/script.json b/test/fixtures/script.json index e0ef536..b5286cf 100644 --- a/test/fixtures/script.json +++ b/test/fixtures/script.json @@ -165,6 +165,10 @@ "stack": [ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" ] + }, + { + "asm": "OP_RETURN aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd", + "script": "6a24aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd" } ], "invalid": { diff --git a/test/fixtures/templates.json b/test/fixtures/templates.json index 1756821..d7873dd 100644 --- a/test/fixtures/templates.json +++ b/test/fixtures/templates.json @@ -494,6 +494,27 @@ "hash": "ffffff" } ] + }, + "witnessCommitment": { + "inputs": [], + "outputs": [ + { + "exception": "", + "commitment": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd" + }, + { + "description": "wrong OPCODE at the start", + "scriptPubKeyHex": "6024aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd" + }, + { + "description": "wrong length marker", + "scriptPubKeyHex": "6a23aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4dcd" + }, + { + "description": "commitment of wrong length", + "scriptPubKeyHex": "6a23aa21a9ed4db4fb830efe3e804337413ffe8ad7393af301e0ec8e71b6e6f2b860a56f4d" + } + ] } } } diff --git a/test/templates.js b/test/templates.js index 33e36f3..6e9a5b1 100644 --- a/test/templates.js +++ b/test/templates.js @@ -53,7 +53,8 @@ describe('script-templates', function () { 'witnessPubKeyHash', 'witnessScriptHash', 'multisig', - 'nullData' + 'nullData', + 'witnessCommitment' ].forEach(function (name) { var inputType = bscript[name].input var outputType = bscript[name].output @@ -106,6 +107,12 @@ describe('script-templates', function () { it('returns ' + expected + ' for ' + f.output, function () { var output = bscript.fromASM(f.output) + if (name.toLowerCase() === 'nulldata' && f.type === bscript.types.WITNESS_COMMITMENT) { + return + } + if (name.toLowerCase() === 'witnesscommitment' && f.type === bscript.types.NULLDATA) { + return + } assert.strictEqual(outputType.check(output), expected) }) } @@ -401,6 +408,43 @@ describe('script-templates', function () { }) }) + describe('witnessCommitment.output', function () { + fixtures.valid.forEach(function (f) { + if (f.type !== 'witnesscommitment') return + if (!f.scriptPubKey) return + + var commitment = new Buffer(f.witnessCommitment, 'hex') + var scriptPubKey = bscript.witnessCommitment.output.encode(commitment) + + it('encodes to ' + f.scriptPubKey, function () { + assert.strictEqual(bscript.toASM(scriptPubKey), f.scriptPubKey) + }) + + it('decodes to ' + commitment.toString('hex'), function () { + assert.deepEqual(bscript.witnessCommitment.output.decode(scriptPubKey), commitment) + }) + }) + + fixtures.invalid.witnessCommitment.outputs.forEach(function (f) { + if (f.commitment) { + var hash = new Buffer(f.commitment, 'hex') + it('throws on bad encode data', function () { + assert.throws(function () { + bscript.witnessCommitment.output.encode(hash) + }, new RegExp(f.exception)) + }) + } + + if (f.scriptPubKeyHex) { + it('.decode throws on ' + f.description, function () { + assert.throws(function () { + bscript.witnessCommitment.output.decode(new Buffer(f.scriptPubKeyHex, 'hex')) + }, new RegExp(f.exception)) + }) + } + }) + }) + describe('nullData.output', function () { fixtures.valid.forEach(function (f) { if (f.type !== 'nulldata') return