fix negative number arithmetic!

This commit is contained in:
Manuel Araoz 2014-03-18 12:08:26 -03:00
parent 19e15f91ca
commit 5505491e8d
5 changed files with 87 additions and 63 deletions

View File

@ -119,7 +119,10 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
case OP_14:
case OP_15:
case OP_16:
this.stack.push(bigintToBuffer(opcode - OP_1 + 1));
var opint = opcode - OP_1 + 1;
var opbuf = bigintToBuffer(opint);
console.log('op'+opcode+' = '+opint+', '+buffertools.toHex(opbuf));
this.stack.push(opbuf);
break;
case OP_NOP:
@ -352,8 +355,6 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
case OP_SIZE:
// (in -- in size)
var value = bignum(this.stackTop().length);
//var topSize = util.bytesNeededToStore(castBigint(this.stackTop()).toNumber());
//var value = bignum(topSize);
this.stack.push(bigintToBuffer(value));
break;
@ -396,6 +397,10 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
// (x1 x2 - bool)
var v1 = this.stackTop(2);
var v2 = this.stackTop(1);
console.log('equal');
console.log(v1);
console.log(v2);
var value = buffertools.compare(v1, v2) === 0;
// OP_NOTEQUAL is disabled because it would be too easy to say
@ -476,6 +481,9 @@ ScriptInterpreter.prototype.eval = function eval(script, tx, inIndex, hashType,
// (x1 x2 -- out)
var v1 = castBigint(this.stackTop(2));
var v2 = castBigint(this.stackTop(1));
console.log('add');
console.log(v1);
console.log(v2);
var num;
switch (opcode) {
case OP_ADD:
@ -874,7 +882,6 @@ var castBigint = ScriptInterpreter.castBigint = function castBigint(v) {
if (!v.length) {
return bignum(0);
}
// Arithmetic operands must be in range [-2^31...2^31]
if (v.length > 4) {
throw new Error("Bigint cast overflow (> 4 bytes)");
@ -883,46 +890,55 @@ var castBigint = ScriptInterpreter.castBigint = function castBigint(v) {
var w = new Buffer(v.length);
v.copy(w);
w = buffertools.reverse(w);
if (w[0] & 0x80) {
w[0] &= 0x7f;
return bignum.fromBuffer(w).neg();
console.log('v ='+buffertools.toHex(w));
var isNeg = w[0] & 0x80;
if (isNeg) {
for (var i = 0; i<w.length; i++) {
console.log('before = '+w[i]);
w[i] = ~w[i];
console.log('after = '+w[i]);
}
console.log('w ='+buffertools.toHex(w));
return bignum.fromBuffer(w).add(bignum(1)).neg();
} else {
// Positive number
return bignum.fromBuffer(w);
}
};
var padSign = function(b) {
var c;
if (b[0] & 0x80) {
c = new Buffer(b.length + 1);
b.copy(c, 1);
c[0] = 0;
} else {
c = b;
}
return c;
}
var bigintToBuffer = ScriptInterpreter.bigintToBuffer = function bigintToBuffer(v) {
if ("number" === typeof v) {
v = bignum(v);
}
var b, c;
var cmp = v.cmp(0);
if (cmp > 0) {
console.log('positive');
b = v.toBuffer();
if (b[0] & 0x80) {
c = new Buffer(b.length + 1);
b.copy(c, 1);
c[0] = 0;
return buffertools.reverse(c);
} else {
return buffertools.reverse(b);
}
c = padSign(b);
c = buffertools.reverse(c);
} else if (cmp == 0) {
return new Buffer([]);
c = new Buffer([]);
} else {
console.log('negative '+v);
b = v.neg().toBuffer();
if (b[0] & 0x80) {
c = new Buffer(b.length + 1);
b.copy(c, 1);
c[0] = 0x80;
return buffertools.reverse(c);
} else {
b[0] |= 0x80;
return buffertools.reverse(b);
}
console.log(b);
c = padSign(b);
c = Util.negativeBuffer(c);
c = buffertools.reverse(c);
}
return c;
};
ScriptInterpreter.prototype.getResult = function getResult() {

View File

@ -2,6 +2,7 @@
var chai = chai || require('chai');
var bitcore = bitcore || require('../bitcore');
var buffertools = require('buffertools');
var should = chai.should();
var testdata = testdata || require('./testdata');
@ -22,6 +23,19 @@ describe('ScriptInterpreter', function() {
var si = new ScriptInterpreter();
should.exist(si);
});
var data = [
[0, ''],
[1, '01'],
[-1, 'ff'],
];
data.forEach(function(datum) {
var i = datum[0];
var hex = datum[1];
it('bigintToBuffer should work for ' + i, function() {
var result = ScriptInterpreter.bigintToBuffer(i);
buffertools.toHex(result).should.equal(hex);
});
});
var i = 0;
testdata.dataScriptValid.forEach(function(datum) {
if (datum.length < 2) throw new Error('Invalid test data');
@ -34,7 +48,7 @@ describe('ScriptInterpreter', function() {
ScriptInterpreter.verify(Script.fromHumanReadable(scriptSig),
Script.fromHumanReadable(scriptPubKey),
null, 0, 0, // tx, output index, and hashtype
function (err, result) {
function(err, result) {
should.not.exist(err);
result.should.equal(true);
done();
@ -67,8 +81,3 @@ describe('ScriptInterpreter', function() {
});

View File

@ -15,8 +15,8 @@ var examples = [
];
describe('Examples', function() {
before(mute);
after(unmute);
//before(mute);
//after(unmute);
examples.forEach(function(example) {
it('valid '+example, function() {
var ex = require('../examples/'+example);

View File

@ -99,6 +99,8 @@ describe('util', function() {
[4294967295, 'ffffffff00'],
[4294967296, '0000000001'],
[4294967297, '0100000001'],
[2147483647, 'ffffff7f'],
[-2147483647, '01000080'],
//[-4294967295, 'feffffffffffffff'],
//[-4294967296, 'feffffffffffffff'],
//[-4294967297, 'feffffffffffffff'],

View File

@ -98,7 +98,7 @@ var formatBuffer = exports.formatBuffer = function (buffer, maxLen) {
var valueToBigInt = exports.valueToBigInt = function (valueBuffer) {
if (Buffer.isBuffer(valueBuffer)) {
return bignum.fromBuffer(valueBuffer, {endian: 'little', size: 8});
return bignum.fromBuffer(valueBuffer, {endian: 'little', size: 'auto'});
} else {
return valueBuffer;
}
@ -108,16 +108,10 @@ var bigIntToValue = exports.bigIntToValue = function (valueBigInt) {
if (Buffer.isBuffer(valueBigInt)) {
return valueBigInt;
} else {
return valueBigInt.toBuffer({endian: 'little', size: 8});
return valueBigInt.toBuffer({endian: 'little', size: 'auto'});
}
};
var intTo64Bits = function(integer) {
return {
hi: Math.floor(integer / 4294967296),
lo: (integer & 0xFFFFFFFF) >>> 0
};
};
var fitsInNBits = function(integer, n) {
// TODO: make this efficient!!!
return integer.toString(2).replace('-','').length < n;
@ -127,6 +121,21 @@ exports.bytesNeededToStore = bytesNeededToStore = function(integer) {
return Math.ceil(((integer).toString(2).replace('-','').length + 1)/ 8);
};
exports.negativeBuffer = negativeBuffer = function(b) {
// implement two-complement negative
var c = new Buffer(b.length);
// negate each byte
for (var i=0; i<b.length; i++){
c[i] = ~b[i];
}
// add one
for (var i=b.length - 1; i>=0; i--){
c[i] += 1;
if (c[i] !== 0) break;
}
console.log('negative of '+buffertools.toHex(b)+' is '+buffertools.toHex(c));
return c;
}
exports.intToBuffer = function(integer) {
var size = bytesNeededToStore(integer);
@ -139,27 +148,15 @@ exports.intToBuffer = function(integer) {
if (si.lenght === 1) {
si = '0' + si;
}
buf.word8((neg?-1:1)*parseInt(si, 16));
buf.word8(parseInt(si, 16));
}
return buf.buffer();
var data = null;
if (fitsInNBits(integer, 8)) {
data = new Buffer(1);
data.writeInt8(integer, 0);
} else if (fitsInNBits(integer, 16)) {
data = new Buffer(2);
data.writeInt16LE(integer, 0);
} else if (fitsInNBits(integer, 32)) {
data = new Buffer(4);
data.writeInt32LE(integer, 0);
} else {
var x = intTo64Bits(integer);
data = new Buffer(8);
data.writeInt32LE(x.hi, 0); // high part contains sign information (signed)
data.writeUInt32LE(x.lo, 4); // low part encoded as unsigned integer
var ret = buf.buffer();
if (neg) {
ret = buffertools.reverse(ret);
ret = negativeBuffer(ret);
ret = buffertools.reverse(ret);
}
return data;
return ret;
};
var formatValue = exports.formatValue = function (valueBuffer) {