script parsing.

This commit is contained in:
Christopher Jeffrey 2016-03-29 14:32:59 -07:00
parent caf52c0579
commit 00e7fcc1ad
8 changed files with 195 additions and 96 deletions

View File

@ -42,7 +42,7 @@ function Coin(tx, index) {
this.version = options.version; this.version = options.version;
this.height = options.height; this.height = options.height;
this.value = options.value; this.value = options.value;
this.script = options.script; this.script = bcoin.script(options.script);
this.coinbase = options.coinbase; this.coinbase = options.coinbase;
this.hash = options.hash; this.hash = options.hash;
this.index = options.index; this.index = options.index;
@ -117,7 +117,7 @@ Coin.prototype.toJSON = function toJSON() {
version: this.version, version: this.version,
height: this.height, height: this.height,
value: utils.btc(this.value), value: utils.btc(this.value),
script: utils.toHex(this.script.encode()), script: utils.toHex(bcoin.protocol.framer(this.script)),
coinbase: this.coinbase, coinbase: this.coinbase,
hash: this.hash ? utils.revHex(this.hash) : null, hash: this.hash ? utils.revHex(this.hash) : null,
index: this.index index: this.index

View File

@ -21,9 +21,9 @@ function Input(options, tx) {
assert(typeof options.script !== 'string'); assert(typeof options.script !== 'string');
this.prevout = options.prevout; this.prevout = options.prevout;
this.script = options.script || new bcoin.script([]); this.script = bcoin.script(options.script);
this.sequence = options.sequence == null ? 0xffffffff : options.sequence; this.sequence = options.sequence == null ? 0xffffffff : options.sequence;
this.witness = options.witness || new bcoin.script.witness([]); this.witness = bcoin.script.witness(options.witness);
this._mutable = !tx || (tx instanceof bcoin.mtx); this._mutable = !tx || (tx instanceof bcoin.mtx);
if (options.coin) if (options.coin)
@ -205,8 +205,8 @@ Input.prototype.toJSON = function toJSON() {
index: this.prevout.index index: this.prevout.index
}, },
coin: this.coin ? this.coin.toJSON() : null, coin: this.coin ? this.coin.toJSON() : null,
script: utils.toHex(this.script.encode()), script: utils.toHex(bcoin.protocol.framer.script(this.script)),
witness: utils.toHex(this.witness.encode()), witness: utils.toHex(bcoin.protocol.framer.witness(this.witness)),
sequence: this.sequence sequence: this.sequence
}; };
}; };
@ -218,8 +218,8 @@ Input._fromJSON = function _fromJSON(json) {
index: json.prevout.index index: json.prevout.index
}, },
coin: json.coin ? bcoin.coin._fromJSON(json.coin) : null, coin: json.coin ? bcoin.coin._fromJSON(json.coin) : null,
script: new bcoin.script(new Buffer(json.script, 'hex')), script: bcoin.protocol.parser.parseScript(new Buffer(json.script, 'hex')),
witness: new bcoin.script.witness(new Buffer(json.witness, 'hex')), witness: bcoin.protocol.parser.parseWitness(new Buffer(json.witness, 'hex')),
sequence: json.sequence sequence: json.sequence
}; };
}; };

View File

@ -140,10 +140,10 @@ MTX.prototype.addInput = function addInput(options, index) {
input = bcoin.input(options, this); input = bcoin.input(options, this);
if (options.script) if (options.script instanceof bcoin.script)
input.script = options.script.clone(); input.script = options.script.clone();
if (options.witness) if (options.witness instanceof bcoin.script.witness)
input.witness = options.witness.clone(); input.witness = options.witness.clone();
this.inputs.push(input); this.inputs.push(input);
@ -666,8 +666,10 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) {
output = this.outputs[index]; output = this.outputs[index];
assert(output); assert(output);
if (options.script) if (options.script instanceof bcoin.script)
output.script = options.script.clone(); output.script = options.script.clone();
else if (options.script)
output.script = bcoin.script(options.script);
else else
output.script = Script.createOutputScript(options); output.script = Script.createOutputScript(options);
}; };

View File

@ -29,7 +29,7 @@ function Output(options, tx) {
} }
this.value = utils.satoshi(value || new bn(0)); this.value = utils.satoshi(value || new bn(0));
this.script = options.script || new bcoin.script([]); this.script = bcoin.script(options.script);
this._mutable = !tx || (tx instanceof bcoin.mtx); this._mutable = !tx || (tx instanceof bcoin.mtx);
// For safety: do not allow usage of // For safety: do not allow usage of
@ -114,7 +114,7 @@ Output.prototype.inspect = function inspect() {
Output.prototype.toJSON = function toJSON() { Output.prototype.toJSON = function toJSON() {
return { return {
value: utils.btc(this.value), value: utils.btc(this.value),
script: utils.toHex(this.script.encode()) script: utils.toHex(bcoin.protocol.framer.script(this.script))
}; };
}; };

View File

@ -335,7 +335,7 @@ Framer.utxo = function _utxo(coin, writer) {
p.writeU32(coin.version); p.writeU32(coin.version);
p.writeU32(height); p.writeU32(height);
p.write64(coin.value); p.write64(coin.value);
p.writeVarBytes(coin.script.encode()); Framer.script(coin.script, p);
if (!writer) if (!writer)
p = p.render(); p = p.render();
@ -355,7 +355,7 @@ Framer.coin = function _coin(coin, extended, writer) {
p.writeU32(coin.version); p.writeU32(coin.version);
p.writeU32(height); p.writeU32(height);
p.write64(coin.value); p.write64(coin.value);
p.writeVarBytes(coin.script.encode()); Framer.script(coin.script, p);
p.writeU8(coin.coinbase ? 1 : 0); p.writeU8(coin.coinbase ? 1 : 0);
if (extended) { if (extended) {
@ -398,7 +398,7 @@ Framer.input = function _input(input, writer) {
p.writeHash(input.prevout.hash); p.writeHash(input.prevout.hash);
p.writeU32(input.prevout.index); p.writeU32(input.prevout.index);
p.writeVarBytes(input.script.encode()); Framer.script(input.script, p);
p.writeU32(input.sequence); p.writeU32(input.sequence);
if (!writer) if (!writer)
@ -413,7 +413,7 @@ Framer.output = function _output(output, writer) {
assert(output.value.byteLength() <= 8); assert(output.value.byteLength() <= 8);
p.write64(output.value); p.write64(output.value);
p.writeVarBytes(output.script.encode()); Framer.script(output.script, p);
if (!writer) if (!writer)
p = p.render(); p = p.render();
@ -456,12 +456,28 @@ Framer.witnessTX = function _witnessTX(tx, writer) {
return p; return p;
}; };
Framer.witnessBlockSize = function witnessBlockSize(block) { // Scripts require extra magic since they're
return Framer.witnessBlock(block, new BufferWriter())._witnessSize; // so goddamn bizarre. Normally in an "encoded"
}; // script we don't include the varint size
// because scripthashes don't include them. This
// is why script.encode/decode is separate
// from the framer and parser.
Framer.script = function _script(script, writer) {
var data;
Framer.witnessTXSize = function witnessTXSize(tx) { p = new BufferWriter(writer);
return Framer.witnessTXSize(tx, new BufferWriter())._witnessSize;
if (script.encode)
data = script.encode();
else
data = script.raw || bcoin.script.encode(script.code);
p.writeVarBytes(data);
if (!writer)
p = p.render();
return p;
}; };
Framer.witness = function _witness(witness, writer) { Framer.witness = function _witness(witness, writer) {
@ -672,6 +688,54 @@ Framer.alert = function alert(data, writer) {
return p; return p;
}; };
// Witness size
Framer.blockSizes = function blockSizes(block) {
var sizes = Framer.witnessBlock(block, new BufferWriter());
return {
size: sizes._size,
witnessSize: sizes._witnessSize
};
};
Framer.txSizes = function txSizes(tx) {
var sizes = Framer.renderTX(tx, true, new BufferWriter());
return {
size: sizes._size,
witnessSize: sizes._witnessSize
};
};
// Size with witness (if present)
Framer.blockRealSize = function blockRealSize(block) {
return Framer.blockSizes(block).size;
};
Framer.txRealSize = function txRealSize(tx) {
return Framer.txSizes(tx).size;
};
// Size without witness
Framer.blockSize = function blockSize(block) {
return Framer.block(block, new BufferWriter())._size;
};
Framer.txSize = function txSize(tx) {
return Framer.renderTX(tx, false, new BufferWriter())._size;
};
// Virtual size
Framer.blockVsize = function blockVsize(block) {
var sizes = this.blockSizes(block);
var base = sizes.size - sizes.witnessSize;
return (base * 4 + sizes.witnessSize + 3) / 4 | 0;
};
Framer.txVsize = function txVsize(tx) {
var sizes = this.txSizes(tx);
var base = sizes.size - sizes.witnessSize;
return (base * 4 + sizes.witnessSize + 3) / 4 | 0;
};
/** /**
* Expose * Expose
*/ */

View File

@ -405,7 +405,7 @@ Parser.parseInput = function parseInput(p) {
hash = p.readHash('hex'); hash = p.readHash('hex');
index = p.readU32(); index = p.readU32();
script = new bcoin.script(p.readVarBytes()); script = Parser.parseScript(p);
sequence = p.readU32(); sequence = p.readU32();
return { return {
@ -413,6 +413,7 @@ Parser.parseInput = function parseInput(p) {
hash: hash, hash: hash,
index: index index: index
}, },
coin: null,
script: script, script: script,
sequence: sequence, sequence: sequence,
_size: p.end() _size: p.end()
@ -426,7 +427,7 @@ Parser.parseOutput = function parseOutput(p) {
p.start(); p.start();
value = p.read64(); value = p.read64();
script = new bcoin.script(p.readVarBytes()); script = Parser.parseScript(p);
return { return {
value: value, value: value,
@ -444,7 +445,7 @@ Parser.parseUTXO = function parseUTXO(p) {
version = p.readU32(); version = p.readU32();
height = p.readU32(); height = p.readU32();
value = p.read64(); value = p.read64();
script = new bcoin.script(p.readVarBytes()); script = Parser.parseScript(p);
if (height === 0x7fffffff) if (height === 0x7fffffff)
height = -1; height = -1;
@ -467,7 +468,7 @@ Parser.parseCoin = function parseCoin(p, extended) {
version = p.readU32(); version = p.readU32();
height = p.readU32(); height = p.readU32();
value = p.read64(); value = p.read64();
script = new bcoin.script(p.readVarBytes()); script = Parser.parseScript(p);
coinbase = p.readU8() === 1; coinbase = p.readU8() === 1;
if (extended) { if (extended) {
@ -510,7 +511,7 @@ Parser.parseTX = function parseTX(p) {
tx = Parser.parseInput(p); tx = Parser.parseInput(p);
txIn[i] = tx; txIn[i] = tx;
txIn[i].witness = new bcoin.script.witness([]); txIn[i].witness = { items: [] };
} }
outCount = p.readVarint(); outCount = p.readVarint();
@ -611,9 +612,23 @@ Parser.parseWitnessTX = function parseWitnessTX(p) {
}; };
}; };
Parser.parseScript = function parseScript(p) {
var data;
p = new BufferReader(p);
p.start();
data = p.readVarBytes();
return {
raw: data,
code: bcoin.script.decode(data),
_size: p.end()
};
};
Parser.parseWitness = function parseWitness(p) { Parser.parseWitness = function parseWitness(p) {
var items = []; var items = [];
var witness, chunkCount, i; var chunkCount, i;
p = new BufferReader(p); p = new BufferReader(p);
p.start(); p.start();
@ -623,10 +638,10 @@ Parser.parseWitness = function parseWitness(p) {
for (i = 0; i < chunkCount; i++) for (i = 0; i < chunkCount; i++)
items.push(p.readVarBytes()); items.push(p.readVarBytes());
witness = new bcoin.script.witness(items); return {
witness._size = p.end(); items: items,
_size: p.end()
return witness; };
}; };
Parser.parseReject = function parseReject(p) { Parser.parseReject = function parseReject(p) {

View File

@ -16,13 +16,19 @@ var STACK_FALSE = new Buffer([]);
var STACK_NEGATE = new Buffer([0xff]); var STACK_NEGATE = new Buffer([0xff]);
function Witness(items) { function Witness(items) {
if (items instanceof Witness)
return items;
if (!(this instanceof Witness)) if (!(this instanceof Witness))
return new Witness(items); return new Witness(items);
if (Buffer.isBuffer(items)) if (!items)
this.items = Witness.decode(items); items = [];
else if (items)
this.items = items || []; if (items.items)
items = items.items;
this.items = items || [];
this.redeem = null; this.redeem = null;
} }
@ -31,18 +37,6 @@ Witness.prototype.inspect = function inspect() {
return Script.format(this.items); return Script.format(this.items);
}; };
Witness.prototype.encode = function encode() {
return bcoin.protocol.framer.witness(this);
};
Witness.encode = function encode(witness) {
return bcoin.protocol.framer.witness(witness);
};
Witness.decode = function decode(buf) {
return bcoin.protocol.parser.parseWitness(buf).items;
};
Witness.prototype.clone = function clone() { Witness.prototype.clone = function clone() {
return new Witness(this.items.slice()); return new Witness(this.items.slice());
}; };
@ -91,19 +85,11 @@ Witness.fromString = function fromString(items) {
items = items.trim().split(/\s+/); items = items.trim().split(/\s+/);
// Remove OP_ prefixes and lowercase
for (i = 0; i < items.length; i++) { for (i = 0; i < items.length; i++) {
op = items[i].toLowerCase(); op = items[i].toLowerCase();
if (op.indexOf('op_') === 0) if (op.indexOf('op_') === 0)
op = op.slice(3); op = op.slice(3);
items[i] = op;
}
// Convert OP_FALSE to 0, convert OP_1-OP_16
// to number literals, convert -1 to OP_1NEGATE.
// Convert hex strings to arrays.
for (i = 0; i < items.length; i++) {
op = items[i];
if (op === '-1' || op === '1negate') { if (op === '-1' || op === '1negate') {
op = STACK_NEGATE; op = STACK_NEGATE;
@ -115,13 +101,12 @@ Witness.fromString = function fromString(items) {
op = new Buffer([+op]); op = new Buffer([+op]);
} else { } else {
symbol = 'OP_' + op.toUpperCase(); symbol = 'OP_' + op.toUpperCase();
if (opcodes[symbol] == null) { if (opcodes[symbol] == null) {
if (op[0] === '[') if (op[0] === '[')
op = op.slice(1, -1); op = op.slice(1, -1);
if (op.indexOf('0x') === 0) if (op.indexOf('0x') === 0)
op = op.substring(2); op = op.substring(2);
assert(utils.isHex(op), 'Non hex-string.'); assert(utils.isHex(op), 'Non-stack item in witness string.');
op = new Buffer(op, 'hex'); op = new Buffer(op, 'hex');
} else { } else {
assert(false, 'Non-stack item in witness string.'); assert(false, 'Non-stack item in witness string.');
@ -445,6 +430,9 @@ Stack.isStack = function isStack(obj) {
*/ */
function Script(code) { function Script(code) {
if (code instanceof Script)
return code;
if (!(this instanceof Script)) if (!(this instanceof Script))
return new Script(code); return new Script(code);
@ -454,9 +442,16 @@ function Script(code) {
} else { } else {
if (!code) if (!code)
code = []; code = [];
assert(Array.isArray(code)); if (code.code) {
this.raw = null; this.raw = code.raw || null;
this.code = code; this.code = code.code;
if (!this.code)
this.code = Script.decode(this.raw);
} else {
assert(Array.isArray(code));
this.raw = null;
this.code = code;
}
} }
this.redeem = null; this.redeem = null;
@ -2331,37 +2326,22 @@ Script.fromString = function fromString(code) {
code = code.trim().split(/\s+/); code = code.trim().split(/\s+/);
// Remove OP_ prefixes and lowercase
for (i = 0; i < code.length; i++) {
op = code[i].toLowerCase();
if (op.indexOf('op_') === 0)
op = op.slice(3);
code[i] = op;
}
// Convert OP_FALSE to 0, convert OP_1-OP_16
// to number literals, convert -1 to OP_1NEGATE.
// Convert hex strings to arrays.
for (i = 0; i < code.length; i++) { for (i = 0; i < code.length; i++) {
op = code[i]; op = code[i];
if (op === '-1') if (op === '-1')
op = '1negate'; op = '1negate';
else if (op === '0' || op === 'false')
op = '0';
else if (op === 'true')
op = '1';
else if (+op >= 1 && +op <= 16)
op = +op + '';
symbol = 'OP_' + op.toUpperCase(); symbol = (op + '').toUpperCase();
if (symbol.indexOf('OP_') !== 0)
symbol = 'OP_' + op;
if (opcodes[symbol] == null) { if (opcodes[symbol] == null) {
if (op[0] === '[') if (op[0] === '[')
op = op.slice(1, -1); op = op.slice(1, -1);
if (op.indexOf('0x') === 0) if (op.indexOf('0x') === 0)
op = op.substring(2); op = op.substring(2);
assert(utils.isHex(op), 'Non hex-string.'); assert(utils.isHex(op), 'Unknown opcode.');
op = new Buffer(op, 'hex'); op = new Buffer(op, 'hex');
code[i] = op; code[i] = op;
continue; continue;
@ -2404,7 +2384,7 @@ Script.toSmall = function toSmall(op) {
Script.fromSymbolic = function fromSymbolic(items) { Script.fromSymbolic = function fromSymbolic(items) {
var code = new Array(items.length); var code = new Array(items.length);
var i, op, symbol; var i, op;
for (i = 0; i < items.length; i++) { for (i = 0; i < items.length; i++) {
op = items[i]; op = items[i];
@ -2414,24 +2394,16 @@ Script.fromSymbolic = function fromSymbolic(items) {
continue; continue;
} }
op = (op + '').toLowerCase();
if (op.indexOf('op_') === 0)
op = op.slice(3);
if (+op === -1) if (+op === -1)
op = '1negate'; op = '1negate';
else if (+op === 0 || op === 'false')
op = '0';
else if (+op === 1 || op === 'true')
op = '1';
else if (+op >= 1 && +op <= 16)
op = +op + '';
symbol = 'OP_' + op.toUpperCase(); op = (op + '').toUpperCase();
if (op.indexOf('OP_') !== 0)
op = 'OP_' + op;
code[i] = opcodes[symbol]; assert(opcodes[op] != null, 'Unknown opcode.');
assert(code[i] != null, 'Unknown opcode.'); code[i] = opcodes[op];
} }
return new Script(code); return new Script(code);

View File

@ -66,6 +66,52 @@ function TX(data, block, index) {
} }
} }
TX.prototype.clone = function clone() {
var copy, i;
copy = {
version: this.version,
inputs: [],
outputs: [],
locktime: this.locktime,
flag: this.flag,
ts: this.ts,
block: this.block,
index: this.index,
height: this.height
};
for (i = 0; i < this.inputs.length; i++) {
copy.inputs.push({
prevout: {
hash: this.inputs[i].prevout.hash,
index: this.inputs[i].prevout.index
},
coin: null,
script: {
code: this.inputs[i].script.code.slice(),
raw: this.inputs[i].script.raw
},
witness: {
items: this.inputs[i].witness.items.slice()
},
sequence: this.inputs[i].sequence
});
}
for (i = 0; i < this.outputs.length; i++) {
copy.outputs.push({
value: this.outputs[i].value.clone(),
script: {
code: this.inputs[i].script.code.slice(),
raw: this.inputs[i].script.raw
}
});
}
return new TX(copy);
};
TX.prototype.setBlock = function setBlock(block, index) { TX.prototype.setBlock = function setBlock(block, index) {
this.ts = block.ts; this.ts = block.ts;
this.block = block.hash('hex'); this.block = block.hash('hex');
@ -252,7 +298,7 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
for (i = 0; i < copy.outputs.length; i++) { for (i = 0; i < copy.outputs.length; i++) {
if (i !== index) { if (i !== index) {
copy.outputs[i].script = new Script([]); copy.outputs[i].script = new Script([]);
copy.outputs[i].value = new bn('ffffffffffffffff', 'hex'); copy.outputs[i].value = utils.U64;
} }
} }