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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ function Output(options, tx) {
}
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);
// For safety: do not allow usage of
@ -114,7 +114,7 @@ Output.prototype.inspect = function inspect() {
Output.prototype.toJSON = function toJSON() {
return {
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(height);
p.write64(coin.value);
p.writeVarBytes(coin.script.encode());
Framer.script(coin.script, p);
if (!writer)
p = p.render();
@ -355,7 +355,7 @@ Framer.coin = function _coin(coin, extended, writer) {
p.writeU32(coin.version);
p.writeU32(height);
p.write64(coin.value);
p.writeVarBytes(coin.script.encode());
Framer.script(coin.script, p);
p.writeU8(coin.coinbase ? 1 : 0);
if (extended) {
@ -398,7 +398,7 @@ Framer.input = function _input(input, writer) {
p.writeHash(input.prevout.hash);
p.writeU32(input.prevout.index);
p.writeVarBytes(input.script.encode());
Framer.script(input.script, p);
p.writeU32(input.sequence);
if (!writer)
@ -413,7 +413,7 @@ Framer.output = function _output(output, writer) {
assert(output.value.byteLength() <= 8);
p.write64(output.value);
p.writeVarBytes(output.script.encode());
Framer.script(output.script, p);
if (!writer)
p = p.render();
@ -456,12 +456,28 @@ Framer.witnessTX = function _witnessTX(tx, writer) {
return p;
};
Framer.witnessBlockSize = function witnessBlockSize(block) {
return Framer.witnessBlock(block, new BufferWriter())._witnessSize;
};
// Scripts require extra magic since they're
// 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) {
return Framer.witnessTXSize(tx, new BufferWriter())._witnessSize;
p = new BufferWriter(writer);
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) {
@ -672,6 +688,54 @@ Framer.alert = function alert(data, writer) {
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
*/

View File

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

View File

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

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) {
this.ts = block.ts;
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++) {
if (i !== index) {
copy.outputs[i].script = new Script([]);
copy.outputs[i].value = new bn('ffffffffffffffff', 'hex');
copy.outputs[i].value = utils.U64;
}
}