Browse Source

Merge remote-tracking branch 'maraoz/test/Transaction'

Conflicts:
	Transaction.js
	test/data/sighash.json
	test/test.sighash.js
patch-2
Ryan X. Charles 11 years ago
parent
commit
c36d7aa4a1
  1. 7
      Script.js
  2. 99
      ScriptInterpreter.js
  3. 6
      Transaction.js
  4. 3
      src/eckey.cc
  5. 1
      test/data/sighash.json
  6. 48
      test/test.Transaction.js
  7. 7
      test/test.sighash.js

7
Script.js

@ -74,9 +74,12 @@ Script.prototype.parse = function() {
}; };
Script.prototype.isPushOnly = function() { Script.prototype.isPushOnly = function() {
for (var i = 0; i < this.chunks.length; i++) for (var i = 0; i < this.chunks.length; i++) {
if (!Buffer.isBuffer(this.chunks[i])) var op = this.chunks[i];
if (!Buffer.isBuffer(op) && op > OP_16) {
return false; return false;
}
}
return true; return true;
}; };

99
ScriptInterpreter.js

@ -48,6 +48,7 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
executeStep.call(this, callback); executeStep.call(this, callback);
function executeStep(cb) { function executeStep(cb) {
try {
// Once all chunks have been processed, execution ends // Once all chunks have been processed, execution ends
if (pc >= script.chunks.length) { if (pc >= script.chunks.length) {
// Execution stack must be empty at the end of the script // Execution stack must be empty at the end of the script
@ -668,8 +669,10 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
} }
var keys = []; var keys = [];
for (var i = 0, l = keysCount; i < l; i++) { for (var i = 0, l = keysCount; i < l; i++) {
keys.push(this.stackPop()); var pubkey = this.stackPop()
keys.push(pubkey);
} }
var sigsCount = castInt(this.stackPop()); var sigsCount = castInt(this.stackPop());
if (sigsCount < 0 || sigsCount > keysCount) { if (sigsCount < 0 || sigsCount > keysCount) {
throw new Error("OP_CHECKMULTISIG sigsCount out of bounds"); throw new Error("OP_CHECKMULTISIG sigsCount out of bounds");
@ -690,10 +693,11 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
// Convert to binary // Convert to binary
var scriptCode = Script.fromChunks(scriptChunks); var scriptCode = Script.fromChunks(scriptChunks);
// Drop the signatures, since a signature can't sign itself
var that = this; var that = this;
sigs.forEach(function(sig) { sigs.forEach(function(sig) {
// check each signature is canonical
that.isCanonicalSignature(new Buffer(sig)); that.isCanonicalSignature(new Buffer(sig));
// Drop the signatures for the subscript, since a signature can't sign itself
scriptCode.findAndDelete(sig); scriptCode.findAndDelete(sig);
}); });
@ -705,9 +709,9 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
function checkMultiSigStep() { function checkMultiSigStep() {
if (success && sigsCount > 0) { if (success && sigsCount > 0) {
var sig = sigs[isig]; var sig = sigs[isig];
var key = keys[ikey]; var pubkey = keys[ikey];
checkSig(sig, key, scriptCode, tx, inIndex, hashType, function(e, result) { checkSig(sig, pubkey, scriptCode, tx, inIndex, hashType, function(e, result) {
if (!e && result) { if (!e && result) {
isig++; isig++;
sigsCount--; sigsCount--;
@ -760,6 +764,9 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
} else { } else {
executeStep.call(this, cb); executeStep.call(this, cb);
} }
} catch (e) {
cb(e);
}
} }
}; };
@ -866,9 +873,9 @@ ScriptInterpreter.prototype.getResult = function getResult() {
return castBool(this.stack[this.stack.length - 1]); return castBool(this.stack[this.stack.length - 1]);
}; };
// Use ScriptInterpreter.verifyFull instead // WARN: Use ScriptInterpreter.verifyFull instead
ScriptInterpreter.verify = ScriptInterpreter.verify =
function verify(scriptSig, scriptPubKey, txTo, n, hashType, callback) { function verify(scriptSig, scriptPubKey, tx, n, hashType, callback) {
if ("function" !== typeof callback) { if ("function" !== typeof callback) {
throw new Error("ScriptInterpreter.verify() requires a callback"); throw new Error("ScriptInterpreter.verify() requires a callback");
} }
@ -877,7 +884,7 @@ ScriptInterpreter.verify =
var si = new ScriptInterpreter(); var si = new ScriptInterpreter();
// Evaluate scripts // Evaluate scripts
si.evalTwo(scriptSig, scriptPubKey, txTo, n, hashType, function(err) { si.evalTwo(scriptSig, scriptPubKey, tx, n, hashType, function(err) {
if (err) { if (err) {
callback(err); callback(err);
return; return;
@ -892,8 +899,8 @@ ScriptInterpreter.verify =
return si; return si;
}; };
ScriptInterpreter.prototype.verifyStep4 = function(scriptSig, scriptPubKey, ScriptInterpreter.prototype.verifyStep4 = function(callback, siCopy) {
txTo, nIn, hashType, callback, siCopy) { // 4th step, check P2SH subscript evaluated to true
if (siCopy.stack.length == 0) { if (siCopy.stack.length == 0) {
callback(null, false); callback(null, false);
return; return;
@ -903,91 +910,105 @@ ScriptInterpreter.prototype.verifyStep4 = function(scriptSig, scriptPubKey,
} }
ScriptInterpreter.prototype.verifyStep3 = function(scriptSig, ScriptInterpreter.prototype.verifyStep3 = function(scriptSig,
scriptPubKey, txTo, nIn, hashType, callback, siCopy) { scriptPubKey, tx, nIn, hashType, callback, siCopy) {
if (this.stack.length == 0) {
// 3rd step, check result (stack should contain true)
// if stack is empty, script considered invalid
if (this.stack.length === 0) {
callback(null, false); callback(null, false);
return; return;
} }
// if top of stack contains false, script evaluated to false
if (castBool(this.stackBack()) == false) { if (castBool(this.stackBack()) == false) {
callback(null, false); callback(null, false);
return; return;
} }
// if not P2SH, we're done // if not P2SH, script evaluated to true
if (!this.opts.verifyP2SH || !scriptPubKey.isP2SH()) { if (!this.opts.verifyP2SH || !scriptPubKey.isP2SH()) {
callback(null, true); callback(null, true);
return; return;
} }
// if P2SH, scriptSig should be push-only
if (!scriptSig.isPushOnly()) { if (!scriptSig.isPushOnly()) {
callback(null, false); callback(null, false);
return; return;
} }
if (siCopy.length === 0) // P2SH script should exist
if (siCopy.length === 0) {
throw new Error('siCopy should have length != 0'); throw new Error('siCopy should have length != 0');
}
var subscript = new Script(siCopy.stackPop()); var subscript = new Script(siCopy.stackPop());
var that = this; var that = this;
siCopy.eval(subscript, txTo, nIn, hashType, function(err) { // evaluate the P2SH subscript
if (err) callback(err); siCopy.eval(subscript, tx, nIn, hashType, function(err) {
else that.verifyStep4(scriptSig, scriptPubKey, txTo, nIn, if (err) return callback(err);
hashType, callback, siCopy); that.verifyStep4(callback, siCopy);
}); });
}; };
ScriptInterpreter.prototype.verifyStep2 = function(scriptSig, scriptPubKey, ScriptInterpreter.prototype.verifyStep2 = function(scriptSig, scriptPubKey,
txTo, nIn, hashType, callback, siCopy) { tx, nIn, hashType, callback, siCopy) {
var siCopy;
if (this.opts.verifyP2SH) { if (this.opts.verifyP2SH) {
siCopy = new ScriptInterpreter(this.opts);
this.stack.forEach(function(item) { this.stack.forEach(function(item) {
siCopy.stack.push(item); siCopy.stack.push(item);
}); });
} }
var that = this; var that = this;
this.eval(scriptPubKey, txTo, nIn, hashType, function(err) { // 2nd step, evaluate scriptPubKey
if (err) callback(err); this.eval(scriptPubKey, tx, nIn, hashType, function(err) {
else that.verifyStep3(scriptSig, scriptPubKey, txTo, nIn, if (err) return callback(err);
that.verifyStep3(scriptSig, scriptPubKey, tx, nIn,
hashType, callback, siCopy); hashType, callback, siCopy);
}); });
}; };
ScriptInterpreter.prototype.verifyFull = function(scriptSig, scriptPubKey,
tx, nIn, hashType, callback) {
var that = this;
// 1st step, evaluate scriptSig
this.eval(scriptSig, tx, nIn, hashType, function(err) {
if (err) return callback(err);
that.verifyStep2(scriptSig, scriptPubKey, tx, nIn,
hashType, callback);
});
};
ScriptInterpreter.verifyFull = ScriptInterpreter.verifyFull =
function verifyFull(scriptSig, scriptPubKey, txTo, nIn, hashType, function verifyFull(scriptSig, scriptPubKey, tx, nIn, hashType,
opts, callback) { opts, callback) {
var si = new ScriptInterpreter(opts); var si = new ScriptInterpreter(opts);
si.verifyFull(scriptSig, scriptPubKey, si.verifyFull(scriptSig, scriptPubKey,
txTo, nIn, hashType, callback); tx, 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);
}
});
};
var checkSig = ScriptInterpreter.checkSig = var checkSig = ScriptInterpreter.checkSig =
function(sig, pubkey, scriptCode, tx, n, hashType, callback) { function(sig, pubkey, scriptCode, tx, n, hashType, callback) {
// https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
if (!sig.length) { if (!sig.length) {
callback(null, false); callback(null, false);
return; return;
} }
if (hashType == 0) { // If the hash-type value is 0, then it is replaced by the last_byte of the signature.
if (hashType === 0) {
hashType = sig[sig.length - 1]; hashType = sig[sig.length - 1];
} else if (hashType != sig[sig.length - 1]) { } else if (hashType != sig[sig.length - 1]) {
callback(null, false); callback(null, false);
return; return;
} }
// Then the last byte of the signature is always deleted. (hashType removed)
sig = sig.slice(0, sig.length - 1); sig = sig.slice(0, sig.length - 1);
// Signature verification requires a special hash procedure // Signature verification requires a special hash procedure
@ -995,7 +1016,9 @@ var checkSig = ScriptInterpreter.checkSig =
// Verify signature // Verify signature
var key = new Key(); var key = new Key();
if (pubkey.length === 0) pubkey = new Buffer('00', 'hex');
key.public = pubkey; key.public = pubkey;
key.verifySignature(hash, sig, callback); key.verifySignature(hash, sig, callback);
}; };

6
Transaction.js

@ -64,7 +64,6 @@ TransactionIn.prototype.getOutpointHash = function getOutpointHash() {
if ("undefined" !== typeof this.o.outHashCache) { if ("undefined" !== typeof this.o.outHashCache) {
return this.o.outHashCache; return this.o.outHashCache;
} }
return this.o.outHashCache = this.o.slice(0, 32); return this.o.outHashCache = this.o.slice(0, 32);
}; };
@ -385,8 +384,9 @@ Transaction.Serializer = TransactionSignatureSerializer;
var oneBuffer = function() { var oneBuffer = function() {
// bug present in bitcoind which must be also present in bitcore // bug present in bitcoind which must be also present in bitcore
// see https://bitcointalk.org/index.php?topic=260595 // see https://bitcointalk.org/index.php?topic=260595
var ret = new Buffer(1); var ret = new Buffer(32);
ret.writeUInt8(1, 0); ret.writeUInt8(1, 0);
for (var i=1; i<32; i++) ret.writeUInt8(0, i);
return ret; // return 1 bug return ret; // return 1 bug
}; };
@ -412,7 +412,7 @@ Transaction.prototype.hashForSignature =
// Append hashType // Append hashType
var hashBuf = new Put().word32le(hashType).buffer(); var hashBuf = new Put().word32le(hashType).buffer();
buffer = Buffer.concat([buffer, hashBuf]); buffer = Buffer.concat([buffer, hashBuf]);
return buffertools.reverse(util.twoSha256(buffer)); return util.twoSha256(buffer);
}; };
/** /**

3
src/eckey.cc

@ -309,8 +309,9 @@ Key::SetPublic(Local<String> property, Local<Value> value, const AccessorInfo& i
Key* key = node::ObjectWrap::Unwrap<Key>(info.Holder()); Key* key = node::ObjectWrap::Unwrap<Key>(info.Holder());
Handle<Object> buffer = value->ToObject(); Handle<Object> buffer = value->ToObject();
const unsigned char *data = (const unsigned char*) Buffer::Data(buffer); const unsigned char *data = (const unsigned char*) Buffer::Data(buffer);
ec_key_st* ret = o2i_ECPublicKey(&(key->ec), &data, Buffer::Length(buffer));
if (!o2i_ECPublicKey(&(key->ec), &data, Buffer::Length(buffer))) { if (!ret) {
// TODO: Error // TODO: Error
return; return;
} }

1
test/data/sighash.json

@ -1,5 +1,6 @@
[ [
["raw_transaction, script, input_index, hashType, signature_hash (result)"], ["raw_transaction, script, input_index, hashType, signature_hash (result)"],
["0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae", 0, 1, "c21469f396d266507fd339292bd8ff0a6d4b29538b914265387a4d17e4839d25"],
["907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000004ab65ababfd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de802000000096aab5253ab52000052ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000009ab53526500636a52ab599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec229", "", 2, 1864164639, "31af167a6cf3f9d5f6875caa4d31704ceb0eba078d132b78dab52c3b8997317e"], ["907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000004ab65ababfd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de802000000096aab5253ab52000052ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000009ab53526500636a52ab599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec229", "", 2, 1864164639, "31af167a6cf3f9d5f6875caa4d31704ceb0eba078d132b78dab52c3b8997317e"],
["a0aa3126041621a6dea5b800141aa696daf28408959dfb2df96095db9fa425ad3f427f2f6103000000015360290e9c6063fa26912c2e7fb6a0ad80f1c5fea1771d42f12976092e7a85a4229fdb6e890000000001abc109f6e47688ac0e4682988785744602b8c87228fcef0695085edf19088af1a9db126e93000000000665516aac536affffffff8fe53e0806e12dfd05d67ac68f4768fdbe23fc48ace22a5aa8ba04c96d58e2750300000009ac51abac63ab5153650524aa680455ce7b000000000000499e50030000000008636a00ac526563ac5051ee030000000003abacabd2b6fe000000000003516563910fb6b5", "65", 0, -1391424484, "48d6a1bd2cd9eec54eb866fc71209418a950402b5d7e52363bfb75c98e141175"], ["a0aa3126041621a6dea5b800141aa696daf28408959dfb2df96095db9fa425ad3f427f2f6103000000015360290e9c6063fa26912c2e7fb6a0ad80f1c5fea1771d42f12976092e7a85a4229fdb6e890000000001abc109f6e47688ac0e4682988785744602b8c87228fcef0695085edf19088af1a9db126e93000000000665516aac536affffffff8fe53e0806e12dfd05d67ac68f4768fdbe23fc48ace22a5aa8ba04c96d58e2750300000009ac51abac63ab5153650524aa680455ce7b000000000000499e50030000000008636a00ac526563ac5051ee030000000003abacabd2b6fe000000000003516563910fb6b5", "65", 0, -1391424484, "48d6a1bd2cd9eec54eb866fc71209418a950402b5d7e52363bfb75c98e141175"],
["6e7e9d4b04ce17afa1e8546b627bb8d89a6a7fefd9d892ec8a192d79c2ceafc01694a6a7e7030000000953ac6a51006353636a33bced1544f797f08ceed02f108da22cd24c9e7809a446c61eb3895914508ac91f07053a01000000055163ab516affffffff11dc54eee8f9e4ff0bcf6b1a1a35b1cd10d63389571375501af7444073bcec3c02000000046aab53514a821f0ce3956e235f71e4c69d91abe1e93fb703bd33039ac567249ed339bf0ba0883ef300000000090063ab65000065ac654bec3cc504bcf499020000000005ab6a52abac64eb060100000000076a6a5351650053bbbc130100000000056a6aab53abd6e1380100000000026a51c4e509b8", "acab655151", 0, 479279909, "2a3d95b09237b72034b23f2d2bb29fa32a58ab5c6aa72f6aafdfa178ab1dd01c"], ["6e7e9d4b04ce17afa1e8546b627bb8d89a6a7fefd9d892ec8a192d79c2ceafc01694a6a7e7030000000953ac6a51006353636a33bced1544f797f08ceed02f108da22cd24c9e7809a446c61eb3895914508ac91f07053a01000000055163ab516affffffff11dc54eee8f9e4ff0bcf6b1a1a35b1cd10d63389571375501af7444073bcec3c02000000046aab53514a821f0ce3956e235f71e4c69d91abe1e93fb703bd33039ac567249ed339bf0ba0883ef300000000090063ab65000065ac654bec3cc504bcf499020000000005ab6a52abac64eb060100000000076a6a5351650053bbbc130100000000056a6aab53abd6e1380100000000026a51c4e509b8", "acab655151", 0, 479279909, "2a3d95b09237b72034b23f2d2bb29fa32a58ab5c6aa72f6aafdfa178ab1dd01c"],

48
test/test.Transaction.js

@ -67,37 +67,53 @@ describe('Transaction', function() {
*/ */
// Verify that known valid transactions are intepretted correctly // Verify that known valid transactions are intepretted correctly
var coreTest = function(data, valid) { var coreTest = function(data, valid) {
buffertools.extend();
data.forEach(function(datum) { data.forEach(function(datum) {
if (datum.length < 3) return; if (datum.length < 3) return;
var raw = datum[1]; var raw = datum[1];
var verifyP2SH = datum[2]; var verifyP2SH = datum[2];
var testTx = parse_test_transaction(datum);
var tx = testTx.transaction;
it.skip((valid ? '' : 'in') + 'valid tx=' + raw, function(done) { describe((valid ? '' : 'in') + 'valid tx=' + raw, function() {
var cb = function(err, results) { it('should parse correctly', function() {
should.not.exist(err); buffertools.toHex(tx.serialize()).toLowerCase().should.equal(raw.toLowerCase());
should.exist(results); });
results.should.equal(valid);
done();
};
var testTx = parse_test_transaction(datum); var inputs = tx.inputs();
buffertools.toHex(testTx.transaction.serialize()).should.equal(raw); var j = 0;
var inputs = testTx.transaction.inputs(); inputs.forEach(function(input) {
for (var i = 0; i < inputs.length; i++) { var i = j;
var input = inputs[i]; j += 1;
buffertools.reverse(input[0]); it('should validate input #' + i, function(done) {
var outpointHash = new Buffer(input[0].length);
input[0].copy(outpointHash);
input[0] = buffertools.reverse(outpointHash);
input[0] = buffertools.toHex(input[0]); input[0] = buffertools.toHex(input[0]);
var mapKey = [input]; var mapKey = [input];
var scriptPubKey = testTx.inputs[mapKey]; var scriptPubKey = testTx.inputs[mapKey];
if (!scriptPubKey) throw new Error('Bad test: '+datum); if (!scriptPubKey) throw new Error('Bad test: ' + datum);
testTx.transaction.verifyInput( tx.verifyInput(
i, i,
scriptPubKey, { scriptPubKey, {
verifyP2SH: verifyP2SH, verifyP2SH: verifyP2SH,
dontVerifyStrictEnc: true dontVerifyStrictEnc: true
}, },
cb); function(err, results) {
if (valid) {
should.not.exist(err);
should.exist(results);
results.should.equal(valid);
} else {
var invalid = (typeof err !== 'undefined') || results === false;
invalid.should.equal(true);
} }
done();
}
);
});
});
}); });
}); });
}; };

7
test/test.sighash.js

@ -98,8 +98,9 @@ var randomTx = function(single) {
var oneBuffer = function() { var oneBuffer = function() {
// bug present in bitcoind which must be also present in bitcore // bug present in bitcoind which must be also present in bitcore
// see https://bitcointalk.org/index.php?topic=260595 // see https://bitcointalk.org/index.php?topic=260595
var ret = new Buffer(1); var ret = new Buffer(32);
ret.writeUInt8(1, 0); ret.writeUInt8(1, 0);
for (var i=1; i<32; i++) ret.writeUInt8(0, i);
return ret; // return 1 bug return ret; // return 1 bug
}; };
@ -125,7 +126,7 @@ var signatureHashOld = function(tx, script, inIndex, hashType) {
// Append hashType // Append hashType
var hashBuf = new Put().word32le(hashType).buffer(); var hashBuf = new Put().word32le(hashType).buffer();
buffer = Buffer.concat([buffer, hashBuf]); buffer = Buffer.concat([buffer, hashBuf]);
return buffertools.reverse(util.twoSha256(buffer)); return util.twoSha256(buffer);
}; };
@ -156,7 +157,7 @@ describe('Transaction sighash (#hashForSignature)', function() {
var scriptPubKey = new Script(new Buffer(datum[1], 'hex')); var scriptPubKey = new Script(new Buffer(datum[1], 'hex'));
var input_index = parseInt(datum[2]); var input_index = parseInt(datum[2]);
var hashType = parseInt(datum[3]); var hashType = parseInt(datum[3]);
var sighash = datum[4]; var sighash = buffertools.toHex(buffertools.reverse(new Buffer(datum[4],'hex')));
it('should validate correctly ' + buffertools.toHex(raw_tx), function() { it('should validate correctly ' + buffertools.toHex(raw_tx), function() {
var tx = new Transaction(); var tx = new Transaction();
tx.parse(raw_tx); tx.parse(raw_tx);

Loading…
Cancel
Save