Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
Merge branch 'matiu-feat/num2bin' into cash
Browse files Browse the repository at this point in the history
  • Loading branch information
nitsujlangston committed Apr 24, 2018
2 parents 90ddaba + c11b406 commit c0717e7
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 4 deletions.
174 changes: 172 additions & 2 deletions lib/script/interpreter.js
Expand Up @@ -9,6 +9,7 @@ var Hash = require('../crypto/hash');
var Signature = require('../crypto/signature');
var PublicKey = require('../publickey');


/**
* Bitcoin transactions contain scripts. Each input has a script called the
* scriptSig, and each output has a script called the scriptPubkey. To validate
Expand Down Expand Up @@ -402,6 +403,92 @@ Interpreter.prototype.checkPubkeyEncoding = function(buf) {
return true;
};



/**
*
* Check the buffer is minimally encoded (see https://github.com/bitcoincashorg/spec/blob/master/may-2018-reenabled-opcodes.md#op_bin2num)
*
*
*/

Interpreter._isMinimallyEncoded = function(buf) {
if (buf.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE ) {
return false;
}

if (buf.length > 0) {
// Check that the number is encoded with the minimum possible number
// of bytes.
//
// If the most-significant-byte - excluding the sign bit - is zero
// then we're not minimal. Note how this test also rejects the
// negative-zero encoding, 0x80.
if ((buf[buf.length-1] & 0x7f) == 0) {
// One exception: if there's more than one byte and the most
// significant bit of the second-most-significant-byte is set it
// would conflict with the sign bit. An example of this case is
// +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) == 0) {
return false;
}
}
}
return true;
};

/**
*
* minimally encode the buffer content
*
* @param {number} nMaxNumSize (max allowed size)
*/
Interpreter._minimallyEncode = function(buf) {
if (buf.length == 0) {
return buf;
}

// If the last byte is not 0x00 or 0x80, we are minimally encoded.
var last = buf[buf.length - 1];
if (last & 0x7f) {
return buf;
}

// If the script is one byte long, then we have a zero, which encodes as an
// empty array.
if (buf.length == 1) {
return Buffer.from('');
}

// If the next byte has it sign bit set, then we are minimaly encoded.
if (buf[buf.length - 2] & 0x80) {
return buf;
}

// We are not minimally encoded, we need to figure out how much to trim.
for (var i = buf.length - 1; i > 0; i--) {
// We found a non zero byte, time to encode.
if (buf[i - 1] != 0) {
if (buf[i - 1] & 0x80) {
// We found a byte with it sign bit set so we need one more
// byte.
buf[i++] = last;
} else {
// the sign bit is clear, we can use it.
buf[i - 1] |= last;
}

return buf.slice(0,i);
}
}

// If we the whole thing is zeros, then we have a zero.
return Buffer.from('');
}



/**
* Based on bitcoind's EvalScript function, with the inner loop moved to
* Interpreter.prototype.step()
Expand All @@ -427,6 +514,7 @@ Interpreter.prototype.evaluate = function() {
return false;
}
} catch (e) {

this.errstr = 'SCRIPT_ERR_UNKNOWN_ERROR: ' + e;
return false;
}
Expand Down Expand Up @@ -567,8 +655,6 @@ Interpreter.prototype.step = function() {
case Opcode.OP_CAT:
case Opcode.OP_SPLIT:

case Opcode.OP_NUM2BIN:
case Opcode.OP_BIN2NUM:
case Opcode.OP_DIV:
case Opcode.OP_MOD:

Expand All @@ -578,6 +664,8 @@ Interpreter.prototype.step = function() {
case Opcode.OP_AND:
case Opcode.OP_OR:
case Opcode.OP_XOR:
case Opcode.OP_BIN2NUM:
case Opcode.OP_NUM2BIN:
// Opcodes that have been reenabled.
if ((self.flags & Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES) == 0) {
return true;
Expand Down Expand Up @@ -1592,6 +1680,88 @@ Interpreter.prototype.step = function() {
}
break;

//
// Conversion operations
//
case Opcode.OP_NUM2BIN: {

// (in -- out)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}

var size = BN.fromScriptNumBuffer(stacktop(-1), fRequireMinimal).toNumber();
if (size > Interpreter.MAX_SCRIPT_ELEMENT_SIZE) {
this.errstr = 'SCRIPT_ERR_PUSH_SIZE';
return false;
}

this.stack.pop();
var rawnum = stacktop(-1);

// Try to see if we can fit that number in the number of
// byte requested.
rawnum=Interpreter._minimallyEncode(rawnum);

if (rawnum.length > size) {
// We definitively cannot.
this.errstr = 'SCRIPT_ERR_IMPOSSIBLE_ENCODING';
return false;
}

// We already have an element of the right size, we
// don't need to do anything.
if (rawnum.length == size) {
this.stack[this.stack.length-1] = rawnum;
break;
}

var signbit = 0x00;
if (rawnum.length > 0) {
signbit = rawnum[rawnum.length - 1] & 0x80;
rawnum[rawnum.length - 1] &= 0x7f;
}

var num = Buffer.alloc(size);
rawnum.copy(num,0);

var l = rawnum.length - 1;
while (l++ < size - 2) {
num[l]=0x00;
}

num[l]=signbit;

this.stack[this.stack.length-1] = num;
}
break;



case Opcode.OP_BIN2NUM: {
// (in -- out)
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}

buf1 = stacktop(-1);
buf2 = Interpreter._minimallyEncode(buf1);

this.stack[this.stack.length - 1] = buf2;

// The resulting number must be a valid number.
if (!Interpreter._isMinimallyEncoded(buf2)) {
this.errstr = 'SCRIPT_ERR_INVALID_NUMBER_RANGE';
return false;
}
}
break;




default:
this.errstr = 'SCRIPT_ERR_BAD_OPCODE';
return false;
Expand Down
1 change: 1 addition & 0 deletions lib/script/script.js
Expand Up @@ -689,6 +689,7 @@ Script.prototype._addBuffer = function(buf, prepend) {
return this;
};


Script.prototype.removeCodeseparators = function() {
var chunks = [];
for (var i = 0; i < this.chunks.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion lib/util/buffer.js
Expand Up @@ -170,7 +170,7 @@ module.exports = {
hexToBuffer: function hexToBuffer(string) {
assert(js.isHexa(string));
return new buffer.Buffer(string, 'hex');
}
},
};

module.exports.NULL_HASH = module.exports.fill(Buffer.alloc(32), 0);
Expand Down
2 changes: 1 addition & 1 deletion test/script/interpreter.js
Expand Up @@ -285,7 +285,7 @@ describe('Interpreter', function() {

// Temporary, until all reenable opcodes are implemented
//
var pendingToImplement = ['NUM2BIN', 'BIN2NUM', 'CAT','SPLIT', 'DIV', 'MOD'];
var pendingToImplement = ['CAT','SPLIT', 'DIV', 'MOD'];
function isPendingToImplement(str) {
for(var i in pendingToImplement) {
if (fullScriptString.indexOf(pendingToImplement[i])!=-1) {
Expand Down

0 comments on commit c0717e7

Please sign in to comment.