Skip to content

Commit

Permalink
script: refactor script and stack mutation.
Browse files Browse the repository at this point in the history
This allows more reasonable signing behavior and eliminates all polymorphism.
  • Loading branch information
chjj committed Aug 25, 2017
1 parent 9d74c83 commit d6ce66b
Show file tree
Hide file tree
Showing 33 changed files with 2,713 additions and 1,878 deletions.
12 changes: 5 additions & 7 deletions bench/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ const tx10 = common.readTX('tx10');
const [tx, view] = tx5.getTX();
const end = bench('sigops');

let sigops = 0;

for (let i = 0; i < 100000; i++)
sigops += tx.getSigopsCost(view);
tx.getSigopsCost(view);

end(100000);
}
Expand Down Expand Up @@ -161,10 +159,10 @@ for (let i = 0; i < 100; i++) {
hash: encoding.NULL_HASH,
index: 0
},
script: [
Buffer.allocUnsafe(9),
random.randomBytes(33)
]
script: new Script()
.pushData(Buffer.allocUnsafe(9))
.pushData(random.randomBytes(33))
.compile()
});
mtx.addOutput({
address: Address.fromHash(random.randomBytes(20)),
Expand Down
43 changes: 22 additions & 21 deletions docs/Scripting.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,50 @@
Scripts are array-like objects with some helper functions.

``` js
var bcoin = require('bcoin');
var assert = require('assert');
var ScriptNum = bcoin.scriptnum;
var opcodes = bcoin.script.opcodes;
const bcoin = require('bcoin');
const assert = require('assert');
const Script = bcoin.script;
const Witness = bcoin.witness;
const Stack = bcoin.stack;

var output = new bcoin.script();
output.push(opcodes.OP_DROP);
output.push(opcodes.OP_ADD);
output.push(new ScriptNum(7));
output.push(opcodes.OP_NUMEQUAL);
const output = new Script();
output.pushSym('OP_DROP');
output.pushSym('OP_ADD');
output.pushInt(7);
output.pushSym('OP_NUMEQUAL');
// Compile the script to its binary representation
// (you must do this if you change something!).
output.compile();
assert(output.getSmall(2) === 7); // compiled as OP_7
output.compile();

var input = new bcoin.script();
input.set(0, 'hello world'); // add some metadata
input.push(new ScriptNum(2));
input.push(new ScriptNum(5));
const input = new Script();
input.setString(0, 'hello world'); // add some metadata
input.pushInt(2);
input.pushInt(5);
input.push(input.shift());
assert(input.getString(2) === 'hello world');
input.compile();

// A stack is another array-like object which contains
// only Buffers (whereas scripts contain Opcode objects).
var stack = new bcoin.stack();
const stack = new Stack();
input.execute(stack);
output.execute(stack);
// Verify the script was successful in its execution:
assert(stack.length === 1);
assert(bcoin.script.bool(stack.pop()) === true);
assert(stack.getBool(-1) === true);
```

Using a witness would be similar, but witnesses do not get executed, they
simply _become_ the stack. The witness object itself is very similar to the
Stack object (an array-like object containing Buffers).

``` js
var witness = new bcoin.witness();
witness.push(new ScriptNum(2));
witness.push(new ScriptNum(5));
witness.push('hello world');
const witness = new Witness();
witness.pushInt(2);
witness.pushInt(5);
witness.pushString('hello world');

var stack = witness.toStack();
const stack = witness.toStack();
output.execute(stack);
```
26 changes: 13 additions & 13 deletions lib/coins/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,29 @@ function compressScript(script, bw) {

// P2PKH -> 0 | key-hash
// Saves 5 bytes.
if (script.isPubkeyhash(true)) {
const hash = script.code[2].data;
const pkh = script.getPubkeyhash(true);
if (pkh) {
bw.writeU8(0);
bw.writeBytes(hash);
bw.writeBytes(pkh);
return bw;
}

// P2SH -> 1 | script-hash
// Saves 3 bytes.
if (script.isScripthash()) {
const hash = script.code[1].data;
const sh = script.getScripthash();
if (sh) {
bw.writeU8(1);
bw.writeBytes(hash);
bw.writeBytes(sh);
return bw;
}

// P2PK -> 2-5 | compressed-key
// Only works if the key is valid.
// Saves up to 35 bytes.
if (script.isPubkey(true)) {
const data = script.code[0].data;
if (publicKeyVerify(data)) {
const key = compressKey(data);
const pk = script.getPubkey(true);
if (pk) {
if (publicKeyVerify(pk)) {
const key = compressKey(pk);
bw.writeBytes(key);
return bw;
}
Expand Down Expand Up @@ -135,9 +135,9 @@ function sizeScript(script) {
if (script.isScripthash())
return 21;

if (script.isPubkey(true)) {
const key = script.code[0].data;
if (publicKeyVerify(key))
const pk = script.getPubkey(true);
if (pk) {
if (publicKeyVerify(pk))
return 33;
}

Expand Down
17 changes: 9 additions & 8 deletions lib/http/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1408,10 +1408,11 @@ RPC.prototype._createTemplate = async function _createTemplate(maxVersion, coinb
// instead of a coinbasevalue.
if (coinbase) {
const tx = attempt.toCoinbase();
const input = tx.inputs[0];

// Pop off the nonces.
tx.inputs[0].script.code.pop();
tx.inputs[0].script.compile();
input.script.pop();
input.script.compile();

if (attempt.witness) {
// We don't include the commitment
Expand All @@ -1420,12 +1421,11 @@ RPC.prototype._createTemplate = async function _createTemplate(maxVersion, coinb
assert(output.script.isCommitment());

// Also not including the witness nonce.
tx.inputs[0].witness.length = 0;
tx.inputs[0].witness.compile();

tx.refresh();
input.witness.clear();
}

tx.refresh();

json.coinbasetxn = {
data: tx.toRaw().toString('hex'),
txid: tx.txid(),
Expand Down Expand Up @@ -2334,13 +2334,14 @@ RPC.prototype._addBlock = async function _addBlock(block) {
// Fix eloipool bug (witness nonce is not present).
if (state.hasWitness() && block.getCommitmentHash()) {
const tx = block.txs[0];
const input = tx.inputs[0];
if (!tx.hasWitness()) {
this.logger.warning('Submitted block had no witness nonce.');
this.logger.debug(tx);

// Recreate witness nonce (all zeroes).
tx.inputs[0].witness.set(0, encoding.ZERO_HASH);
tx.inputs[0].witness.compile();
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();

tx.refresh();
block.refresh();
Expand Down
19 changes: 9 additions & 10 deletions lib/mining/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const policy = require('../protocol/policy');
const encoding = require('../utils/encoding');
const CoinView = require('../coins/coinview');
const Script = require('../script/script');
const ScriptNum = require('../script/scriptnum');
const common = require('./common');
const DUMMY = Buffer.alloc(0);

Expand Down Expand Up @@ -241,24 +240,23 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
const input = new Input();

// Height (required in v2+ blocks)
const height = ScriptNum.fromNumber(this.height);
input.script.push(height);
input.script.pushInt(this.height);

// Coinbase flags.
input.script.push(encoding.ZERO_HASH160);
input.script.pushData(encoding.ZERO_HASH160);

// Smaller nonce for good measure.
input.script.push(util.nonce(4));
input.script.pushData(util.nonce(4));

// Extra nonce: incremented when
// the nonce overflows.
input.script.push(encoding.ZERO_U64);
input.script.pushData(encoding.ZERO_U64);

input.script.compile();

// Set up the witness nonce.
if (this.witness) {
input.witness.set(0, encoding.ZERO_HASH);
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
}

Expand All @@ -281,7 +279,9 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
}

// Padding for the CB height (constant size).
const padding = 5 - input.script.code[0].getSize();
const op = input.script.get(0);
assert(op);
const padding = 5 - op.getSize();
assert(padding >= 0);

// Reserved size.
Expand All @@ -306,11 +306,10 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
}

// Setup coinbase flags (variable size).
input.script.set(1, this.coinbaseFlags);
input.script.setData(1, this.coinbaseFlags);
input.script.compile();

// Setup output script (variable size).
output.script.clear();
output.script.fromAddress(this.address);

cb.refresh();
Expand Down
62 changes: 32 additions & 30 deletions lib/primitives/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,45 +426,39 @@ Address.fromBech32 = function fromBech32(data, network) {
*/

Address.prototype.fromScript = function fromScript(script) {
if (script.isPubkey()) {
this.hash = digest.hash160(script.get(0));
const pk = script.getPubkey();

if (pk) {
this.hash = digest.hash160(pk);
this.type = Address.types.PUBKEYHASH;
this.version = -1;
return this;
}

if (script.isPubkeyhash()) {
this.hash = script.get(2);
const pkh = script.getPubkeyhash();

if (pkh) {
this.hash = pkh;
this.type = Address.types.PUBKEYHASH;
this.version = -1;
return this;
}

if (script.isScripthash()) {
this.hash = script.get(1);
const sh = script.getScripthash();

if (sh) {
this.hash = sh;
this.type = Address.types.SCRIPTHASH;
this.version = -1;
return this;
}

if (script.isWitnessPubkeyhash()) {
this.hash = script.get(1);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
}

if (script.isWitnessScripthash()) {
this.hash = script.get(1);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
}
const program = script.getProgram();

if (script.isWitnessMasthash()) {
this.hash = script.get(1);
if (program && !program.isMalformed()) {
this.hash = program.data;
this.type = Address.types.WITNESS;
this.version = 1;
this.version = program.version;
return this;
}

Expand All @@ -486,17 +480,21 @@ Address.prototype.fromScript = function fromScript(script) {
*/

Address.prototype.fromWitness = function fromWitness(witness) {
const [, pk] = witness.getPubkeyhashInput();

// We're pretty much screwed here
// since we can't get the version.
if (witness.isPubkeyhashInput()) {
this.hash = digest.hash160(witness.get(1));
if (pk) {
this.hash = digest.hash160(pk);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
}

if (witness.isScripthashInput()) {
this.hash = digest.sha256(witness.get(witness.length - 1));
const redeem = witness.getScripthashInput();

if (redeem) {
this.hash = digest.sha256(redeem);
this.type = Address.types.WITNESS;
this.version = 0;
return this;
Expand All @@ -512,15 +510,19 @@ Address.prototype.fromWitness = function fromWitness(witness) {
*/

Address.prototype.fromInputScript = function fromInputScript(script) {
if (script.isPubkeyhashInput()) {
this.hash = digest.hash160(script.get(1));
const [, pk] = script.getPubkeyhashInput();

if (pk) {
this.hash = digest.hash160(pk);
this.type = Address.types.PUBKEYHASH;
this.version = -1;
return this;
}

if (script.isScripthashInput()) {
this.hash = digest.hash160(script.get(script.length - 1));
const redeem = script.getScripthashInput();

if (redeem) {
this.hash = digest.hash160(redeem);
this.type = Address.types.SCRIPTHASH;
this.version = -1;
return this;
Expand Down
2 changes: 1 addition & 1 deletion lib/primitives/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ Block.prototype.getCommitmentHash = function getCommitmentHash(enc) {
for (let i = coinbase.outputs.length - 1; i >= 0; i--) {
const output = coinbase.outputs[i];
if (output.script.isCommitment()) {
hash = output.script.getCommitmentHash();
hash = output.script.getCommitment();
break;
}
}
Expand Down
Loading

0 comments on commit d6ce66b

Please sign in to comment.