Ryan X. Charles
11 years ago
8 changed files with 257 additions and 20 deletions
@ -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