From ab052eb81dc6663b19bff18197fdb81d52508c0e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 18 Sep 2016 14:15:46 -0700 Subject: [PATCH] script: implement bip114v2. --- lib/primitives/address.js | 2 +- lib/script/script.js | 190 +++++++++++++++++++++++++++----------- 2 files changed, 138 insertions(+), 54 deletions(-) diff --git a/lib/primitives/address.js b/lib/primitives/address.js index ea0323fc..70ca0118 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -476,7 +476,7 @@ Address.prototype.fromData = function fromData(data, type, version, network) { data = crypto.sha256(data); } else if (version === 1) { assert(Array.isArray(data)); - data = crypto.getMerkleRoot(data); + throw new Error('MASTv2 creation not implemented.'); } else { throw new Error('Cannot create from version=' + version); } diff --git a/lib/script/script.js b/lib/script/script.js index b0cb3d2c..a6964272 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -3229,14 +3229,14 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { * @param {VerifyFlags} flags * @param {TX} tx * @param {Number} i + * @returns {Boolean} * @throws {ScriptError} */ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { var program = output.toProgram(); var stack = witness.toStack(); - var witnessScript, redeem, j; - var hash, pathdata, depth, path, posdata, pos, root; + var j, witnessScript, redeem; assert(program, 'verifyProgram called on non-witness-program.'); assert((flags & constants.flags.VERIFY_WITNESS) !== 0); @@ -3262,57 +3262,7 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); } } else if ((flags & constants.flags.VERIFY_MAST) && program.version === 1) { - if (program.data.length !== 32) - throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); - - if (stack.length < 3) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - witnessScript = stack.pop(); - redeem = new Script(witnessScript); - - hash = crypto.hash256(witnessScript); - pathdata = stack.pop(); - - if (pathdata.length & 0x1f) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - depth = pathdata.length >>> 5; - - if (depth > 32) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - path = []; - - for (j = 0; j < depth; j++) - path.push(pathdata.slice(j * 32, j * 32 + 32)); - - posdata = stack.pop(); - - if (posdata.length > 4) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - pos = 0; - if (posdata.length > 0) { - if (posdata[posdata.length - 1] === 0x00) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - for (j = 0; j < posdata.length; j++) - pos |= posdata[i] << 8 * j; - - if (pos < 0) - pos += 0x100000000; - } - - if (depth < 32) { - if (pos >= ((1 << depth) >>> 0)) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - } - - root = crypto.checkMerkleBranch(hash, path, pos); - - if (!utils.equal(root, program.data)) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + return Script.verifyMast(program, stack, output, flags, tx, i); } else { // Anyone can spend (we can return true here // if we want to always relay these transactions). @@ -3342,6 +3292,140 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { return true; }; +/** + * Verify a MAST witness program. + * @param {Program} program + * @param {Stack} stack + * @param {Script} output + * @param {VerifyFlags} flags + * @param {TX} tx + * @param {Number} i + * @returns {Boolean} + * @throws {ScriptError} + */ + +Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { + var mastRoot = new BufferWriter(); + var scriptRoot = new BufferWriter(); + var scripts = new BufferWriter(); + var version = 0; + var pathdata, depth, path, posdata, pos; + var metadata, subscripts, ops, script; + var j; + + assert(program.version === 1); + assert((flags & constants.flags.VERIFY_MAST) !== 0); + + if (stack.length < 4) + throw new ScriptError('INVALID_MAST_STACK'); + + metadata = stack.pop(); + if (metadata.length < 1 || metadata.length > 5) + throw new ScriptError('INVALID_MAST_STACK'); + + subscripts = metadata[0]; + if (subscripts === 0 || stack.length < subscripts + 2) + throw new ScriptError('INVALID_MAST_STACK'); + + ops = subscripts; + scriptRoot.writeU8(subscripts); + + if (metadata[metadata.length - 1] === 0x00) + throw new ScriptError('INVALID_MAST_STACK'); + + for (j = 1; j < metadata.length; j++) + version |= metadata[i] << 8 * (j - 1); + + if (version < 0) + version += 0x100000000; + + if (version > 0) { + if (flags & constants.flags.DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) + throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); + } + + mastRoot.writeU32(version); + + pathdata = stack.pop(); + + if (pathdata.length & 0x1f) + throw new ScriptError('INVALID_MAST_STACK'); + + depth = pathdata.length >>> 5; + + if (depth > 32) + throw new ScriptError('INVALID_MAST_STACK'); + + ops += depth; + if (version === 0) { + if (ops > constants.script.MAX_OPS) + throw new ScriptError('OP_COUNT'); + } + + path = []; + + for (j = 0; j < depth; j++) + path.push(pathdata.slice(j * 32, j * 32 + 32)); + + posdata = stack.pop(); + + if (posdata.length > 4) + throw new ScriptError('INVALID_MAST_STACK'); + + pos = 0; + if (posdata.length > 0) { + if (posdata[posdata.length - 1] === 0x00) + throw new ScriptError('INVALID_MAST_STACK'); + + for (j = 0; j < posdata.length; j++) + pos |= posdata[i] << 8 * j; + + if (pos < 0) + pos += 0x100000000; + } + + if (depth < 32) { + if (pos >= ((1 << depth) >>> 0)) + throw new ScriptError('INVALID_MAST_STACK'); + } + + scripts.writeBytes(output.raw); + + for (j = 0; j < subscripts; j++) { + script = stack.pop(); + if (version === 0) { + if ((scripts.written + script.length) > constants.script.MAX_SIZE) + throw new ScriptError('SCRIPT_SIZE'); + } + scriptRoot.writeBytes(crypto.hash256(script)); + scripts.writeBytes(script); + } + + scriptRoot = crypto.hash256(scriptRoot.render()); + scriptRoot = crypto.checkMerkleBranch(scriptRoot, path, pos); + + mastRoot.writeBytes(scriptRoot); + mastRoot = crypto.hash256(mastRoot.render()); + + if (!utils.equal(mastRoot, program.data)) + throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + + if (version === 0) { + for (j = 0; j < stack.length; j++) { + if (stack.get(j).length > constants.script.MAX_PUSH) + throw new ScriptError('PUSH_SIZE'); + } + } + + output = new bcoin.script(scripts.render()); + output.execute(stack, flags, tx, i, 1); + + if (stack.length !== 0) + throw new ScriptError('EVAL_FALSE'); + + return true; +}; + /** * Verify a signature, taking into account sighash type * and whether the signature is historical.