From 278ff826582e36d24b0e8662c06b1c20c735e98b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 15 May 2016 23:51:18 -0700 Subject: [PATCH] FindAndDelete again. --- lib/bcoin/script.js | 102 +++++++++++++++++++++++++++++ test/script-test.js | 153 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 9e700aef..7260476e 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -2026,6 +2026,77 @@ Script.prototype.removeData = function removeData(data) { return index.length; }; +/** + * A more accurate, but slower implementation + * of FindAndDelete. Shouldn't be necessary + * in practice. + * @private + * @param {Buffer} data + * @returns {Buffer} + */ + +Script.prototype.findAndDelete = function findAndDelete(data) { + var total = 0; + var p, raw, a, b, op, size; + + if (data.length === 0) + return total; + + // Compare on the byte level. + raw = this.encode(); + + p = new BufferReader(raw, true); + + for (;;) { + while (p.left() >= data.length && utils.icmp(raw, data, p.offset) === 0) { + a = raw.slice(0, p.offset); + b = raw.slice(p.offset + data.length); + raw = Buffer.concat([a, b]); + p.data = raw; + total++; + } + + if (p.left() === 0) + break; + + op = p.readU8(); + if (op >= 0x01 && op <= 0x4b) { + if (p.left() < op) + break; + p.seek(op); + } else if (op === opcodes.OP_PUSHDATA1) { + if (p.left() < 1) + break; + size = p.readU8(); + if (p.left() < size) + break; + p.seek(size); + } else if (op === opcodes.OP_PUSHDATA2) { + if (p.left() < 2) + break; + size = p.readU16(); + if (p.left() < size) + break; + p.seek(size); + } else if (op === opcodes.OP_PUSHDATA4) { + if (p.left() < 4) + break; + size = p.readU32(); + if (p.left() < size) + break; + p.seek(size); + } + } + + if (total > 0) { + this.code = Script.decode(raw); + if (this.raw) + this.raw = raw; + } + + return total; +}; + /** * Find a data element in a script. * @param {Buffer} data - Data element to match against. @@ -4169,6 +4240,37 @@ Script.encode = function encode(code, writer) { return p; }; +/** + * Encode a single push op. Note that this + * will _always_ be a PUSHDATA. Used for + * findAndDelete. + * @param {Buffer} data + * @returns {Buffer} + */ + +Script.encodePush = function encodePush(data) { + var p = new BufferWriter(); + if (data.length <= 0x4b) { + p.writeU8(data.length); + p.writeBytes(data); + } else if (data.length <= 0xff) { + p.writeU8(opcodes.OP_PUSHDATA1); + p.writeU8(data.length); + p.writeBytes(data); + } else if (data.length <= 0xffff) { + p.writeU8(opcodes.OP_PUSHDATA2); + p.writeU16(data.length); + p.writeBytes(data); + } else if (data.length <= 0xffffffff) { + p.writeU8(opcodes.OP_PUSHDATA4); + p.writeU32(data.length); + p.writeBytes(data); + } else { + assert(false, 'Bad pushdata op.'); + } + return p.render(); +}; + /** * Determine whether GetOp2 should fail * on a given op due to a bad push (either diff --git a/test/script-test.js b/test/script-test.js index d2707c08..15b55fcb 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -352,4 +352,157 @@ describe('Script', function() { }); }); }); + + it('should execute FindAndDelete correctly', function() { + var s, d, expect; + + function del(s) { + s.mutable = true; + delete s.raw; + return s; + } + + s = bcoin.script.fromString('OP_1 OP_2'); + del(s); + d = new bcoin.script(); + expect = s.clone(); + assert.equal(s.findAndDelete(d.encode()), 0); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromString('OP_1 OP_2 OP_3'); + del(s); + d = bcoin.script.fromString('OP_2'); + del(d); + expect = bcoin.script.fromString('OP_1 OP_3'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromString('OP_3 OP_1 OP_3 OP_3 OP_4 OP_3'); + del(s); + d = bcoin.script.fromString('OP_3'); + del(d); + expect = bcoin.script.fromString('OP_1 OP_4'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 4); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0302ff03', 'hex'); + del(s); + d = bcoin.script.fromRaw('0302ff03', 'hex'); + del(d); + expect = new bcoin.script(); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0302ff030302ff03', 'hex'); + del(s); + d = bcoin.script.fromRaw('0302ff03', 'hex'); + del(d); + expect = new bcoin.script(); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 2); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0302ff030302ff03', 'hex'); + del(s); + d = bcoin.script.fromRaw('02', 'hex'); + del(d); + expect = s.clone(); + del(expect); + //assert.equal(s.findAndDelete(d.encode()), 0); + s.findAndDelete(d.encode()); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0302ff030302ff03', 'hex'); + del(s); + d = bcoin.script.fromRaw('ff', 'hex'); + del(d); + expect = s.clone(); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 0); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0302ff030302ff03', 'hex'); + del(s); + d = bcoin.script.fromRaw('03', 'hex'); + del(d); + expect = new bcoin.script([new Buffer([0xff, 0x03]), new Buffer([0xff, 0x03])]); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 2); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('02feed5169', 'hex'); + del(s); + d = bcoin.script.fromRaw('feed51', 'hex'); + del(d); + expect = s.clone(); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 0); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('02feed5169', 'hex'); + del(s); + d = bcoin.script.fromRaw('02feed51', 'hex'); + del(d); + expect = bcoin.script.fromRaw('69', 'hex'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('516902feed5169', 'hex'); + del(s); + d = bcoin.script.fromRaw('feed51', 'hex'); + del(d); + expect = s.clone(); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 0); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('516902feed5169', 'hex'); + del(s); + d = bcoin.script.fromRaw('02feed51', 'hex'); + del(d); + expect = bcoin.script.fromRaw('516969', 'hex'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromString('OP_0 OP_0 OP_1 OP_1'); + del(s); + d = bcoin.script.fromString('OP_0 OP_1'); + del(d); + expect = bcoin.script.fromString('OP_0 OP_1'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromString('OP_0 OP_0 OP_1 OP_0 OP_1 OP_1'); + del(s); + d = bcoin.script.fromString('OP_0 OP_1'); + del(d); + expect = bcoin.script.fromString('OP_0 OP_1'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 2); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0003feed', 'hex'); + del(s); + d = bcoin.script.fromRaw('03feed', 'hex'); + del(d); + expect = bcoin.script.fromRaw('00', 'hex'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + + s = bcoin.script.fromRaw('0003feed', 'hex'); + del(s); + d = bcoin.script.fromRaw('00', 'hex'); + del(d); + expect = bcoin.script.fromRaw('03feed', 'hex'); + del(expect); + assert.equal(s.findAndDelete(d.encode()), 1); + assert.deepEqual(s.encode(), expect.encode()); + }); });