Skip to content

Commit

Permalink
some Transaction tests fixed (canonical signatures)
Browse files Browse the repository at this point in the history
  • Loading branch information
maraoz authored and MattFaus committed Mar 21, 2014
1 parent 7869308 commit b227341
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 186 deletions.
229 changes: 119 additions & 110 deletions ScriptInterpreter.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
var imports = require('soop').imports();
var config = imports.config || require('./config');
var log = imports.log || require('./util/log');
var util = imports.util || require('./util/util');
var Opcode = imports.Opcode || require('./Opcode');
var imports = require('soop').imports();
var config = imports.config || require('./config');
var log = imports.log || require('./util/log');
var util = imports.util || require('./util/util');
var Opcode = imports.Opcode || require('./Opcode');
var buffertools = imports.buffertools || require('buffertools');
var bignum = imports.bignum || require('bignum');
var Util = imports.Util || require('./util/util');
var Script = require('./Script');
var bignum = imports.bignum || require('bignum');
var Util = imports.Util || require('./util/util');
var Script = require('./Script');

var SIGHASH_ALL = 1;
var SIGHASH_NONE = 2;
Expand All @@ -21,7 +21,8 @@ for (var i in Opcode.map) {
var intToBufferSM = Util.intToBufferSM
var bufferSMToInt = Util.bufferSMToInt;

function ScriptInterpreter() {
function ScriptInterpreter(opts) {
this.opts = opts || {};
this.stack = [];
this.disableUnsafeOpcodes = true;
};
Expand Down Expand Up @@ -98,8 +99,7 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,

if (exec && Buffer.isBuffer(opcode)) {
this.stack.push(opcode);
}
else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
} else if (exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
switch (opcode) {
case OP_0:
this.stack.push(new Buffer([]));
Expand Down Expand Up @@ -411,10 +411,13 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
this.stackPop();
this.stackPop();
this.stack.push(new Buffer([value ? 1 : 0]));
console.log(script.toHumanReadable());
if (opcode === OP_EQUALVERIFY) {
if (value) {
this.stackPop();
} else {
console.log(v1);
console.log(v2);
throw new Error("OP_EQUALVERIFY negative");
}
}
Expand Down Expand Up @@ -621,7 +624,7 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
scriptCode.findAndDelete(sig);

//
isCanonicalSignature(new Buffer(sig));
this.isCanonicalSignature(new Buffer(sig));

// Verify signature
checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) {
Expand Down Expand Up @@ -695,8 +698,9 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
var scriptCode = Script.fromChunks(scriptChunks);

// Drop the signatures, since a signature can't sign itself
var that = this;
sigs.forEach(function(sig) {
isCanonicalSignature(new Buffer(sig));
that.isCanonicalSignature(new Buffer(sig));
scriptCode.findAndDelete(sig);
});

Expand Down Expand Up @@ -811,7 +815,7 @@ ScriptInterpreter.prototype.stackTop = function stackTop(offset) {
};

ScriptInterpreter.prototype.stackBack = function stackBack() {
return this.stack[this.stack.length -1];
return this.stack[this.stack.length - 1];
};

/**
Expand Down Expand Up @@ -882,6 +886,7 @@ ScriptInterpreter.prototype.getResult = function getResult() {
return castBool(this.stack[this.stack.length - 1]);
};

// Use ScriptInterpreter.verifyFull instead
ScriptInterpreter.verify =
function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) {
if ("function" !== typeof callback) {
Expand Down Expand Up @@ -912,8 +917,8 @@ ScriptInterpreter.verify =
return si;
};

function verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy) {
ScriptInterpreter.prototype.verifyStep4 = function(scriptSig, scriptPubKey,
txTo, nIn, hashType, callback, siCopy) {
if (siCopy.stack.length == 0) {
callback(null, false);
return;
Expand All @@ -922,19 +927,19 @@ function verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
callback(null, castBool(siCopy.stackBack()));
}

function verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy) {
if (si.stack.length == 0) {
ScriptInterpreter.prototype.verifyStep3 = function(scriptSig,
scriptPubKey, txTo, nIn, hashType, callback, siCopy) {
if (this.stack.length == 0) {
callback(null, false);
return;
}
if (castBool(si.stackBack()) == false) {
if (castBool(this.stackBack()) == false) {
callback(null, false);
return;
}

// if not P2SH, we're done
if (!opts.verifyP2SH || !scriptPubKey.isP2SH()) {
if (!this.opts.verifyP2SH || !scriptPubKey.isP2SH()) {
callback(null, true);
return;
}
Expand All @@ -949,46 +954,48 @@ function verifyStep3(scriptSig, scriptPubKey, txTo, nIn,

var subscript = new Script(siCopy.stackPop());

ok = true;
var that = this;
siCopy.eval(subscript, txTo, nIn, hashType, function(err) {
if (err)
callback(err);
else
verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy);
if (err) callback(err);
else that.verifyStep4(scriptSig, scriptPubKey, txTo, nIn,
hashType, callback, siCopy);
});
}
};

function verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy) {
if (opts.verifyP2SH) {
si.stack.forEach(function(item) {
ScriptInterpreter.prototype.verifyStep2 = function(scriptSig, scriptPubKey,
txTo, nIn, hashType, callback, siCopy) {
if (this.opts.verifyP2SH) {
this.stack.forEach(function(item) {
siCopy.stack.push(item);
});
}

si.eval(scriptPubKey, txTo, nIn, hashType, function(err) {
if (err)
callback(err);
else
verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy);
var that = this;
this.eval(scriptPubKey, txTo, nIn, hashType, function(err) {
if (err) callback(err);
else that.verifyStep3(scriptSig, scriptPubKey, txTo, nIn,
hashType, callback, siCopy);
});
}
};

ScriptInterpreter.verifyFull =
function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType,
opts, callback) {
var si = new ScriptInterpreter();
var siCopy = new ScriptInterpreter();
var si = new ScriptInterpreter(opts);
si.verifyFull(scriptSig, scriptPubKey,
txTo, nIn, hashType, callback);
};

ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey,
txTo, nIn, hashType, callback) {
var siCopy = new ScriptInterpreter(this.opts);
var that = this;
this.eval(scriptSig, txTo, nIn, hashType, function(err) {
if (err) callback(err);
else that.verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
hashType, callback, siCopy);
});

si.eval(scriptSig, txTo, nIn, hashType, function(err) {
if (err)
callback(err);
else
verifyStep2(scriptSig, scriptPubKey, txTo, nIn,
hashType, opts, callback, si, siCopy);
});
};

var checkSig = ScriptInterpreter.checkSig =
Expand Down Expand Up @@ -1019,68 +1026,70 @@ var checkSig = ScriptInterpreter.checkSig =
}
};

var isCanonicalSignature = ScriptInterpreter.isCanonicalSignature = function(sig, opts) {
// See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
// Where R and S are not negative (their first byte has its highest bit not set), and not
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
// in which case a single 0 byte is necessary and even required).

if (!Buffer.isBuffer(sig))
throw new Error("arg should be a Buffer");

opts = opts || {};

var l = sig.length;
if (l < 9) throw new Error("Non-canonical signature: too short");
if (l > 73) throw new Error("Non-canonical signature: too long");

var nHashType = sig[l-1] & (~(SIGHASH_ANYONECANPAY));
if (nHashType < SIGHASH_ALL || nHashType > SIGHASH_SINGLE)
throw new Error("Non-canonical signature: unknown hashtype byte");

if (sig[0] !== 0x30)
throw new Error("Non-canonical signature: wrong type");
if (sig[1] !== l-3)
throw new Error("Non-canonical signature: wrong length marker");

var nLenR = sig[3];
if (5 + nLenR >= l)
throw new Error("Non-canonical signature: S length misplaced");

var nLenS = sig[5+nLenR];
if ( (nLenR+nLenS+7) !== l)
throw new Error("Non-canonical signature: R+S length mismatch");

var rPos = 4;
var R = new Buffer(nLenR);
sig.copy(R, 0, rPos, rPos+ nLenR);
if (sig[rPos-2] !== 0x02)
throw new Error("Non-canonical signature: R value type mismatch");
if (nLenR == 0)
throw new Error("Non-canonical signature: R length is zero");
if (R[0] & 0x80)
throw new Error("Non-canonical signature: R value negative");
if (nLenR > 1 && (R[0] == 0x00) && !(R[1] & 0x80))
throw new Error("Non-canonical signature: R value excessively padded");

var sPos = 6 + nLenR;
var S = new Buffer(nLenS);
sig.copy(S, 0, sPos, sPos+ nLenS);
if (sig[sPos-2] != 0x02)
throw new Error("Non-canonical signature: S value type mismatch");
if (nLenS == 0)
throw new Error("Non-canonical signature: S length is zero");
if (S[0] & 0x80)
throw new Error("Non-canonical signature: S value negative");
if (nLenS > 1 && (S[0] == 0x00) && !(S[1] & 0x80))
throw new Error("Non-canonical signature: S value excessively padded");

if (opts.verifyEvenS) {
if (S[nLenS-1] & 1)
throw new Error("Non-canonical signature: S value odd");
}
return true;
ScriptInterpreter.prototype.isCanonicalSignature = function(sig) {
// See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
// A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
// Where R and S are not negative (their first byte has its highest bit not set), and not
// excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
// in which case a single 0 byte is necessary and even required).

if (!Buffer.isBuffer(sig))
throw new Error("arg should be a Buffer");

// TODO: change to opts.verifyStrictEnc to make the default
// behavior not verify, as in bitcoin core
if (this.opts.dontVerifyStrictEnc) return true;

var l = sig.length;
if (l < 9) throw new Error("Non-canonical signature: too short");
if (l > 73) throw new Error("Non-canonical signature: too long");

var nHashType = sig[l - 1] & (~(SIGHASH_ANYONECANPAY));
if (nHashType < SIGHASH_ALL || nHashType > SIGHASH_SINGLE)
throw new Error("Non-canonical signature: unknown hashtype byte");

if (sig[0] !== 0x30)
throw new Error("Non-canonical signature: wrong type");
if (sig[1] !== l - 3)
throw new Error("Non-canonical signature: wrong length marker");

var nLenR = sig[3];
if (5 + nLenR >= l)
throw new Error("Non-canonical signature: S length misplaced");

var nLenS = sig[5 + nLenR];
if ((nLenR + nLenS + 7) !== l)
throw new Error("Non-canonical signature: R+S length mismatch");

var rPos = 4;
var R = new Buffer(nLenR);
sig.copy(R, 0, rPos, rPos + nLenR);
if (sig[rPos - 2] !== 0x02)
throw new Error("Non-canonical signature: R value type mismatch");
if (nLenR == 0)
throw new Error("Non-canonical signature: R length is zero");
if (R[0] & 0x80)
throw new Error("Non-canonical signature: R value negative");
if (nLenR > 1 && (R[0] == 0x00) && !(R[1] & 0x80))
throw new Error("Non-canonical signature: R value excessively padded");

var sPos = 6 + nLenR;
var S = new Buffer(nLenS);
sig.copy(S, 0, sPos, sPos + nLenS);
if (sig[sPos - 2] != 0x02)
throw new Error("Non-canonical signature: S value type mismatch");
if (nLenS == 0)
throw new Error("Non-canonical signature: S length is zero");
if (S[0] & 0x80)
throw new Error("Non-canonical signature: S value negative");
if (nLenS > 1 && (S[0] == 0x00) && !(S[1] & 0x80))
throw new Error("Non-canonical signature: S value excessively padded");

if (this.opts.verifyEvenS) {
if (S[nLenS - 1] & 1)
throw new Error("Non-canonical signature: S value odd");
}
return true;
};

module.exports = require('soop')(ScriptInterpreter);
19 changes: 11 additions & 8 deletions Transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,10 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
}

return txout;
};
}

Step(
function verifyInputs() {
function verifyInputs(opts) {
var group = this.group();

if (self.isCoinBase()) {
Expand All @@ -278,7 +278,7 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {

outpoints.push(txin.o);

self.verifyInput(n, txout.getScript(), group());
self.verifyInput(n, txout.getScript(), opts, group());
});
},

Expand Down Expand Up @@ -351,11 +351,14 @@ Transaction.prototype.verify = function verify(txCache, blockChain, callback) {
);
};

Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, callback) {
return ScriptInterpreter.verify(this.ins[n].getScript(),
scriptPubKey,
this, n, 0,
callback);
Transaction.prototype.verifyInput = function verifyInput(n, scriptPubKey, opts, callback) {
var valid = ScriptInterpreter.verifyFull(
this.ins[n].getScript(),
scriptPubKey,
this, n, 0,
opts,
callback);
return valid;
};

/**
Expand Down
7 changes: 4 additions & 3 deletions test/test.ScriptInterpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ describe('ScriptInterpreter', function() {
isHex = 1;
} catch (e) {}

if (isHex)
ScriptInterpreter.isCanonicalSignature.bind(sig).should.
throw ();
// ignore non-hex strings
if (isHex) {
ScriptInterpreter.isCanonicalSignature.bind(sig).should.throw();
}
});
});

Expand Down

0 comments on commit b227341

Please sign in to comment.