Merge pull request #190 from maraoz/test/sighash
Test sighash (Transaction.hashForSignature())
This commit is contained in:
commit
9cc70a3b57
10
Opcode.js
10
Opcode.js
|
@ -155,4 +155,14 @@ for (var k in Opcode.map) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Opcode.asList = function() {
|
||||||
|
var keys = [];
|
||||||
|
for (var prop in Opcode.map) {
|
||||||
|
if (Opcode.map.hasOwnProperty(prop)) {
|
||||||
|
keys.push(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = require('soop')(Opcode);
|
module.exports = require('soop')(Opcode);
|
||||||
|
|
|
@ -265,6 +265,8 @@ Script.prototype.getBuffer = function() {
|
||||||
return this.buffer;
|
return this.buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Script.prototype.serialize = Script.prototype.getBuffer;
|
||||||
|
|
||||||
Script.prototype.getStringContent = function(truncate, maxEl) {
|
Script.prototype.getStringContent = function(truncate, maxEl) {
|
||||||
if (truncate === null) {
|
if (truncate === null) {
|
||||||
truncate = true;
|
truncate = true;
|
||||||
|
|
|
@ -445,15 +445,7 @@ Transaction.prototype.hashForSignature =
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone transaction
|
// Clone transaction
|
||||||
var txTmp = new Transaction();
|
var txTmp = new Transaction(this);
|
||||||
this.ins.forEach(function(txin, i) {
|
|
||||||
txTmp.ins.push(new TransactionIn(txin));
|
|
||||||
});
|
|
||||||
this.outs.forEach(function(txout) {
|
|
||||||
txTmp.outs.push(new TransactionOut(txout));
|
|
||||||
});
|
|
||||||
txTmp.version = this.version;
|
|
||||||
txTmp.lock_time = this.lock_time;
|
|
||||||
|
|
||||||
// In case concatenating two scripts ends up with two codeseparators,
|
// In case concatenating two scripts ends up with two codeseparators,
|
||||||
// or an extra one at the end, this prevents all those possible
|
// or an extra one at the end, this prevents all those possible
|
||||||
|
@ -505,10 +497,14 @@ Transaction.prototype.hashForSignature =
|
||||||
} else {
|
} else {
|
||||||
var outsLen;
|
var outsLen;
|
||||||
if (hashTypeMode === SIGHASH_SINGLE) {
|
if (hashTypeMode === SIGHASH_SINGLE) {
|
||||||
// TODO: Untested
|
|
||||||
if (inIndex >= txTmp.outs.length) {
|
if (inIndex >= txTmp.outs.length) {
|
||||||
throw new Error("Transaction.hashForSignature(): SIGHASH_SINGLE " +
|
// bug present in bitcoind which must be also present in bitcore
|
||||||
"no corresponding txout found - out of bounds");
|
// see https://bitcointalk.org/index.php?topic=260595
|
||||||
|
// Transaction.hashForSignature(): SIGHASH_SINGLE
|
||||||
|
// no corresponding txout found - out of bounds
|
||||||
|
var ret = new Buffer(1);
|
||||||
|
ret.writeUInt8(1, 0);
|
||||||
|
return ret; // return 1 bug
|
||||||
}
|
}
|
||||||
outsLen = inIndex + 1;
|
outsLen = inIndex + 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,6 +12,7 @@ var requireWhenAccessed = function(name, file) {
|
||||||
|
|
||||||
requireWhenAccessed('bignum', 'bignum');
|
requireWhenAccessed('bignum', 'bignum');
|
||||||
requireWhenAccessed('base58', 'base58-native');
|
requireWhenAccessed('base58', 'base58-native');
|
||||||
|
requireWhenAccessed('bufferput', 'bufferput');
|
||||||
requireWhenAccessed('buffertools', 'buffertools');
|
requireWhenAccessed('buffertools', 'buffertools');
|
||||||
requireWhenAccessed('config', './config');
|
requireWhenAccessed('config', './config');
|
||||||
requireWhenAccessed('const', './const');
|
requireWhenAccessed('const', './const');
|
||||||
|
|
|
@ -89,6 +89,9 @@ var createBitcore = function(opts) {
|
||||||
b.require(opts.dir + 'browserify-buffertools/buffertools.js', {
|
b.require(opts.dir + 'browserify-buffertools/buffertools.js', {
|
||||||
expose: 'buffertools'
|
expose: 'buffertools'
|
||||||
});
|
});
|
||||||
|
b.require(opts.dir + 'bufferput', {
|
||||||
|
expose: 'bufferput'
|
||||||
|
});
|
||||||
b.require(opts.dir + 'base58-native', {
|
b.require(opts.dir + 'base58-native', {
|
||||||
expose: 'base58-native'
|
expose: 'base58-native'
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
<script src="test.RpcClient.js"></script>
|
<script src="test.RpcClient.js"></script>
|
||||||
<script src="test.Script.js"></script>
|
<script src="test.Script.js"></script>
|
||||||
<script src="test.ScriptInterpreter.js"></script>
|
<script src="test.ScriptInterpreter.js"></script>
|
||||||
|
<script src="test.sighash.js"></script>
|
||||||
<script src="test.SIN.js"></script>
|
<script src="test.SIN.js"></script>
|
||||||
<script src="test.SINKey.js"></script>
|
<script src="test.SINKey.js"></script>
|
||||||
<script src="test.Transaction.js"></script>
|
<script src="test.Transaction.js"></script>
|
||||||
|
|
|
@ -17,13 +17,13 @@ describe('Opcode', function() {
|
||||||
should.exist(Opcode);
|
should.exist(Opcode);
|
||||||
});
|
});
|
||||||
it('should be able to create instance', function() {
|
it('should be able to create instance', function() {
|
||||||
var oc = new Opcode();
|
var oc = new Opcode(81);
|
||||||
should.exist(oc);
|
should.exist(oc);
|
||||||
});
|
});
|
||||||
it('should be able to create some constants', function() {
|
it('should be able to create some constants', function() {
|
||||||
// TODO: test works in node but not in browser
|
// TODO: test works in node but not in browser
|
||||||
for (var i in Opcode.map) {
|
for (var i in Opcode.map) {
|
||||||
eval('var '+i + ' = ' + Opcode.map[i] + ';');
|
eval('var ' + i + ' = ' + Opcode.map[i] + ';');
|
||||||
}
|
}
|
||||||
should.exist(OP_VER);
|
should.exist(OP_VER);
|
||||||
should.exist(OP_HASH160);
|
should.exist(OP_HASH160);
|
||||||
|
@ -31,11 +31,10 @@ describe('Opcode', function() {
|
||||||
should.exist(OP_EQUALVERIFY);
|
should.exist(OP_EQUALVERIFY);
|
||||||
should.exist(OP_CHECKSIG);
|
should.exist(OP_CHECKSIG);
|
||||||
should.exist(OP_CHECKMULTISIG);
|
should.exist(OP_CHECKMULTISIG);
|
||||||
|
});
|
||||||
|
it('#asList should work', function() {
|
||||||
|
var list = Opcode.asList();
|
||||||
|
(typeof(list[0])).should.equal('string');
|
||||||
|
list.length.should.equal(116);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// inspired in bitcoin core test:
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/7d49a9173ab636d118c2a81fc3c3562192e7813a/src/test/sighash_tests.cpp
|
||||||
|
|
||||||
|
var chai = chai || require('chai');
|
||||||
|
var should = chai.should();
|
||||||
|
var bitcore = bitcore || require('../bitcore');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var Script = bitcore.Script;
|
||||||
|
var Opcode = bitcore.Opcode;
|
||||||
|
var util = bitcore.util;
|
||||||
|
var Put = bitcore.Put;
|
||||||
|
var Put = require('bufferput');
|
||||||
|
var buffertools = require('buffertools');
|
||||||
|
|
||||||
|
var seed = 1;
|
||||||
|
// seedable pseudo-random function
|
||||||
|
var random = function() {
|
||||||
|
var x = Math.sin(seed++) * 10000;
|
||||||
|
return x - Math.floor(x);
|
||||||
|
};
|
||||||
|
|
||||||
|
var randInt = function(low, high) {
|
||||||
|
return Math.floor(random() * (high - low + 1) + low);
|
||||||
|
};
|
||||||
|
var randUIntN = function(nBits) {
|
||||||
|
return randInt(0, Math.pow(2, nBits));
|
||||||
|
};
|
||||||
|
var randUInt32 = function() {
|
||||||
|
return randUIntN(32);
|
||||||
|
};
|
||||||
|
var randBool = function() {
|
||||||
|
return random() < 0.5;
|
||||||
|
};
|
||||||
|
var hexAlphabet = '0123456789abcdef';
|
||||||
|
var randHex = function() {
|
||||||
|
return hexAlphabet[randInt(0, 15)];
|
||||||
|
};
|
||||||
|
var randHexN = function(n) {
|
||||||
|
var s = '';
|
||||||
|
while (n--) {
|
||||||
|
s += randHex();
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
var randTxHash = function() {
|
||||||
|
return randHexN(64);
|
||||||
|
};
|
||||||
|
var randPick = function(list) {
|
||||||
|
return list[randInt(0, list.length - 1)];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var opList = Opcode.asList();
|
||||||
|
|
||||||
|
var randomScript = function() {
|
||||||
|
var s = new Script();
|
||||||
|
var ops = randInt(0, 10);
|
||||||
|
for (var i = 0; i < ops; i++) {
|
||||||
|
var op = randPick(opList);
|
||||||
|
s.writeOp(Opcode.map[op]);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
var randomTx = function(single) {
|
||||||
|
var tx = new Transaction({
|
||||||
|
version: randUInt32(),
|
||||||
|
lock_time: randBool() ? randUInt32() : 0
|
||||||
|
});
|
||||||
|
var insN = randInt(1, 5);
|
||||||
|
var outsN = single ? insN : randInt(1, 5);
|
||||||
|
for (var i = 0; i < insN; i++) {
|
||||||
|
var txin = new Transaction.In({
|
||||||
|
oTxHash: randTxHash(),
|
||||||
|
oIndex: randInt(0, 4),
|
||||||
|
script: randomScript().serialize(),
|
||||||
|
sequence: randBool() ? randUInt32() : 0xffffffff
|
||||||
|
});
|
||||||
|
tx.ins.push(txin);
|
||||||
|
}
|
||||||
|
for (i = 0; i < outsN; i++) {
|
||||||
|
var txout = new Transaction.Out({
|
||||||
|
value: new Buffer(8),
|
||||||
|
script: randomScript().serialize()
|
||||||
|
});
|
||||||
|
tx.outs.push(txout);
|
||||||
|
}
|
||||||
|
return tx;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var signatureHashOld = function(tx, script, inIndex, hashType) {
|
||||||
|
if (+inIndex !== inIndex ||
|
||||||
|
inIndex < 0 || inIndex >= tx.ins.length) {
|
||||||
|
throw new Error('Input index "' + inIndex + '" invalid or out of bounds ' +
|
||||||
|
'(' + tx.ins.length + ' inputs)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone transaction
|
||||||
|
var txTmp = new Transaction();
|
||||||
|
tx.ins.forEach(function(txin) {
|
||||||
|
txTmp.ins.push(new Transaction.In(txin));
|
||||||
|
});
|
||||||
|
tx.outs.forEach(function(txout) {
|
||||||
|
txTmp.outs.push(new Transaction.Out(txout));
|
||||||
|
});
|
||||||
|
txTmp.version = tx.version;
|
||||||
|
txTmp.lock_time = tx.lock_time;
|
||||||
|
|
||||||
|
// In case concatenating two scripts ends up with two codeseparators,
|
||||||
|
// or an extra one at the end, this prevents all those possible
|
||||||
|
// incompatibilities.
|
||||||
|
script.findAndDelete(Opcode.map.OP_CODESEPARATOR);
|
||||||
|
|
||||||
|
// Get mode portion of hashtype
|
||||||
|
var hashTypeMode = hashType & 0x1f;
|
||||||
|
|
||||||
|
// Generate modified transaction data for hash
|
||||||
|
var bytes = (new Put());
|
||||||
|
bytes.word32le(tx.version);
|
||||||
|
|
||||||
|
// Serialize inputs
|
||||||
|
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
||||||
|
// Blank out all inputs except current one, not recommended for open
|
||||||
|
// transactions.
|
||||||
|
bytes.varint(1);
|
||||||
|
bytes.put(tx.ins[inIndex].o);
|
||||||
|
bytes.varint(script.buffer.length);
|
||||||
|
bytes.put(script.buffer);
|
||||||
|
bytes.word32le(tx.ins[inIndex].q);
|
||||||
|
} else {
|
||||||
|
bytes.varint(tx.ins.length);
|
||||||
|
for (var i = 0, l = tx.ins.length; i < l; i++) {
|
||||||
|
var txin = tx.ins[i];
|
||||||
|
bytes.put(txin.o);
|
||||||
|
|
||||||
|
// Current input's script gets set to the script to be signed, all others
|
||||||
|
// get blanked.
|
||||||
|
if (inIndex === i) {
|
||||||
|
bytes.varint(script.buffer.length);
|
||||||
|
bytes.put(script.buffer);
|
||||||
|
} else {
|
||||||
|
bytes.varint(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashTypeMode === Transaction.SIGHASH_NONE && inIndex !== i) {
|
||||||
|
bytes.word32le(0);
|
||||||
|
} else {
|
||||||
|
bytes.word32le(tx.ins[i].q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize outputs
|
||||||
|
if (hashTypeMode === Transaction.SIGHASH_NONE) {
|
||||||
|
bytes.varint(0);
|
||||||
|
} else {
|
||||||
|
var outsLen;
|
||||||
|
if (hashTypeMode === Transaction.SIGHASH_SINGLE) {
|
||||||
|
if (inIndex >= txTmp.outs.length) {
|
||||||
|
// bug present in bitcoind which must be also present in bitcore
|
||||||
|
// Transaction.hashForSignature(): SIGHASH_SINGLE
|
||||||
|
// no corresponding txout found - out of bounds
|
||||||
|
var ret = new Buffer(1);
|
||||||
|
ret.writeUInt8(1, 0);
|
||||||
|
return ret; // return 1 bug
|
||||||
|
}
|
||||||
|
outsLen = inIndex + 1;
|
||||||
|
} else {
|
||||||
|
outsLen = tx.outs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.varint(outsLen);
|
||||||
|
for (var i = 0; i < outsLen; i++) {
|
||||||
|
if (hashTypeMode === Transaction.SIGHASH_SINGLE && i !== inIndex) {
|
||||||
|
// Zero all outs except the one we want to keep
|
||||||
|
bytes.put(util.INT64_MAX);
|
||||||
|
bytes.varint(0);
|
||||||
|
} else {
|
||||||
|
bytes.put(tx.outs[i].v);
|
||||||
|
bytes.varint(tx.outs[i].s.length);
|
||||||
|
bytes.put(tx.outs[i].s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.word32le(tx.lock_time);
|
||||||
|
|
||||||
|
var buffer = bytes.buffer();
|
||||||
|
|
||||||
|
// Append hashType
|
||||||
|
buffer = Buffer.concat([buffer, new Buffer([parseInt(hashType), 0, 0, 0])]);
|
||||||
|
|
||||||
|
return util.twoSha256(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('Transaction sighash (#hashForSignature)', function() {
|
||||||
|
for (var i = 0; i < 250; i++) {
|
||||||
|
it('should hash correctly random tx #' + (i + 1), function() {
|
||||||
|
var tx = randomTx();
|
||||||
|
var l = tx.ins.length;
|
||||||
|
for (var i = 0; i < l; i++) {
|
||||||
|
var script = randomScript();
|
||||||
|
var hashType = randUInt32();
|
||||||
|
var h = buffertools.toHex(tx.hashForSignature(script, i, hashType));
|
||||||
|
var oh = buffertools.toHex(signatureHashOld(tx, script, i, hashType));
|
||||||
|
h.should.equal(oh);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue