From 5a1b513b24090d94b541988a75d6da16194f2869 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 17 Mar 2014 16:57:23 -0300 Subject: [PATCH] add isCanonicalSignature check to script interpreter and tests --- ScriptInterpreter.js | 73 ++++++++++++++++++++++++++++++++++ test/test.ScriptInterpreter.js | 24 +++++++++++ test/testdata.js | 5 +++ 3 files changed, 102 insertions(+) diff --git a/ScriptInterpreter.js b/ScriptInterpreter.js index 895fc0170..fc901406d 100644 --- a/ScriptInterpreter.js +++ b/ScriptInterpreter.js @@ -7,6 +7,11 @@ 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; +var SIGHASH_SINGLE = 3; +var SIGHASH_ANYONECANPAY = 80; + // Make opcodes available as pseudo-constants for (var i in Opcode.map) { eval(i + " = " + Opcode.map[i] + ";"); @@ -607,6 +612,9 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, // Remove signature if present (a signature can't sign itself) scriptCode.findAndDelete(sig); + // + isCanonicalSignature(new Buffer(sig)); + // Verify signature checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) { try { @@ -680,6 +688,7 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType, // Drop the signatures, since a signature can't sign itself sigs.forEach(function(sig) { + isCanonicalSignature(new Buffer(sig)); scriptCode.findAndDelete(sig); }); @@ -1056,4 +1065,68 @@ 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> <02> <02> + // 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; +}; + module.exports = require('soop')(ScriptInterpreter); diff --git a/test/test.ScriptInterpreter.js b/test/test.ScriptInterpreter.js index 166fd0cf0..8ede7b42d 100644 --- a/test/test.ScriptInterpreter.js +++ b/test/test.ScriptInterpreter.js @@ -42,6 +42,30 @@ describe('ScriptInterpreter', function() { ); }); }); + + var i = 0; + testdata.dataSigCanonical.forEach(function(datum) { + it('should validate valid canonical signatures', function() { + ScriptInterpreter.isCanonicalSignature(new Buffer(datum,'hex')).should.equal(true); + }); + }); + testdata.dataSigNonCanonical.forEach(function(datum) { + it('should NOT validate invalid canonical signatures', function() { + + var sig; + var isHex; + //is Hex? + try { + sig =new Buffer(datum,'hex'); + isHex=1; + } catch (e) { } + + if (isHex) + ScriptInterpreter.isCanonicalSignature.bind(sig).should.throw(); + }); + }); + + }); diff --git a/test/testdata.js b/test/testdata.js index dd05be767..3eacf6cf6 100644 --- a/test/testdata.js +++ b/test/testdata.js @@ -7,6 +7,8 @@ var dataTxValid = JSON.parse(fs.readFileSync('test/data/tx_valid.json')); var dataTxInvalid = JSON.parse(fs.readFileSync('test/data/tx_invalid.json')); var dataScriptValid = JSON.parse(fs.readFileSync('test/data/script_valid.json')); var dataScriptInvalid = JSON.parse(fs.readFileSync('test/data/script_invalid.json')); +var dataSigCanonical = JSON.parse(fs.readFileSync('test/data/sig_canonical.json')); +var dataSigNonCanonical = JSON.parse(fs.readFileSync('test/data/sig_noncanonical.json')); module.exports.dataValid = dataValid; module.exports.dataInvalid = dataInvalid; @@ -16,3 +18,6 @@ module.exports.dataTxInvalid = dataTxInvalid; module.exports.dataScriptValid = dataScriptValid; module.exports.dataScriptInvalid = dataScriptInvalid; module.exports.dataScriptAll = dataScriptValid.concat(dataScriptInvalid); + +module.exports.dataSigCanonical = dataSigCanonical; +module.exports.dataSigNonCanonical = dataSigNonCanonical;