|
|
|
var BufferReader = require('./bufferreader');
|
|
|
|
var BufferWriter = require('./bufferwriter');
|
|
|
|
var Opcode = require('./opcode');
|
|
|
|
|
|
|
|
var Script = function Script(buf) {
|
|
|
|
if (!(this instanceof Script))
|
|
|
|
return new Script(buf);
|
|
|
|
|
|
|
|
this.chunks = [];
|
|
|
|
|
|
|
|
if (Buffer.isBuffer(buf)) {
|
|
|
|
this.fromBuffer(buf);
|
|
|
|
}
|
|
|
|
else if (typeof buf === 'string') {
|
|
|
|
var str = buf;
|
|
|
|
this.fromString(str);
|
|
|
|
}
|
|
|
|
else if (typeof buf !== 'undefined') {
|
|
|
|
var obj = buf;
|
|
|
|
this.set(obj);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.set = function(obj) {
|
|
|
|
this.chunks = obj.chunks || this.chunks;
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.fromJSON = function(json) {
|
|
|
|
return this.fromString(json);
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.toJSON = function() {
|
|
|
|
return this.toString();
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.fromBuffer = function(buf) {
|
|
|
|
this.chunks = [];
|
|
|
|
|
|
|
|
var br = new BufferReader(buf);
|
|
|
|
while (!br.eof()) {
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
var opcodenum = br.readUInt8();
|
|
|
|
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
var len, buf;
|
|
|
|
if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) {
|
|
|
|
len = opcodenum;
|
|
|
|
this.chunks.push({
|
|
|
|
buf: br.read(len),
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
len: len,
|
|
|
|
opcodenum: opcodenum
|
|
|
|
});
|
|
|
|
} else if (opcodenum === Opcode.map.OP_PUSHDATA1) {
|
|
|
|
len = br.readUInt8();
|
|
|
|
var buf = br.read(len);
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
this.chunks.push({
|
|
|
|
buf: buf,
|
|
|
|
len: len,
|
|
|
|
opcodenum: opcodenum
|
|
|
|
});
|
|
|
|
} else if (opcodenum === Opcode.map.OP_PUSHDATA2) {
|
|
|
|
len = br.readUInt16LE();
|
|
|
|
buf = br.read(len);
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
this.chunks.push({
|
|
|
|
buf: buf,
|
|
|
|
len: len,
|
|
|
|
opcodenum: opcodenum
|
|
|
|
});
|
|
|
|
} else if (opcodenum === Opcode.map.OP_PUSHDATA4) {
|
|
|
|
len = br.readUInt32LE();
|
|
|
|
buf = br.read(len);
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
this.chunks.push({
|
|
|
|
buf: buf,
|
|
|
|
len: len,
|
|
|
|
opcodenum: opcodenum
|
|
|
|
});
|
|
|
|
} else {
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
this.chunks.push(opcodenum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
Script.prototype.toBuffer = function() {
|
|
|
|
var bw = new BufferWriter();
|
|
|
|
|
|
|
|
for (var i = 0; i < this.chunks.length; i++) {
|
|
|
|
var chunk = this.chunks[i];
|
|
|
|
if (typeof chunk === 'number') {
|
|
|
|
var opcodenum = chunk;
|
|
|
|
bw.writeUInt8(opcodenum);
|
|
|
|
} else {
|
|
|
|
var opcodenum = chunk.opcodenum;
|
|
|
|
bw.writeUInt8(chunk.opcodenum);
|
|
|
|
if (opcodenum < Opcode.map.OP_PUSHDATA1) {
|
|
|
|
bw.write(chunk.buf);
|
|
|
|
}
|
|
|
|
else if (opcodenum === Opcode.map.OP_PUSHDATA1) {
|
|
|
|
bw.writeUInt8(chunk.len);
|
|
|
|
bw.write(chunk.buf);
|
|
|
|
}
|
|
|
|
else if (opcodenum === Opcode.map.OP_PUSHDATA2) {
|
|
|
|
bw.writeUInt16LE(chunk.len);
|
|
|
|
bw.write(chunk.buf);
|
|
|
|
}
|
|
|
|
else if (opcodenum === Opcode.map.OP_PUSHDATA4) {
|
|
|
|
bw.writeUInt32LE(chunk.len);
|
|
|
|
bw.write(chunk.buf);
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bw.concat();
|
preserve claimed length and op code
When parsing OP_PUSHDATAX commands, the the length of data might not require
the size integer of OP_PUSHDATAX. For instance, you might write 1 byte, and yet
use OP_PUSHDATA4. We need to record which OP_PUSHDATAX was used so that when we
write the buffer back out, we can write the same one. Also, the claimed length
may be different. For instance, we may OP_PUSHDATA of length 100 to the stack,
but there may only be 50 bytes left in the script. In that case, buf.length and
chunk.len will be different. I'm not sure if that would be considered a valid
script, but in any case, for script analysis, we need both values.
10 years ago
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.fromString = function(str) {
|
|
|
|
this.chunks = [];
|
|
|
|
|
|
|
|
var tokens = str.split(' ');
|
|
|
|
var i = 0;
|
|
|
|
while (i < tokens.length) {
|
|
|
|
var token = tokens[i];
|
|
|
|
var opcode = Opcode(token);
|
|
|
|
var opcodenum = opcode.toNumber();
|
|
|
|
|
|
|
|
if (typeof opcodenum === 'undefined') {
|
|
|
|
opcodenum = parseInt(token);
|
|
|
|
if (opcodenum > 0 && opcodenum < Opcode.map.OP_PUSHDATA1) {
|
|
|
|
this.chunks.push({
|
|
|
|
buf: new Buffer(tokens[i + 1].slice(2), 'hex'),
|
|
|
|
len: opcodenum,
|
|
|
|
opcodenum: opcodenum
|
|
|
|
});
|
|
|
|
i = i + 2;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
throw new Error('Invalid script');
|
|
|
|
}
|
|
|
|
} else if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4) {
|
|
|
|
if (tokens[i + 2].slice(0, 2) != '0x')
|
|
|
|
throw new Error('Pushdata data must start with 0x');
|
|
|
|
this.chunks.push({
|
|
|
|
buf: new Buffer(tokens[i + 2].slice(2), 'hex'),
|
|
|
|
len: parseInt(tokens[i + 1]),
|
|
|
|
opcodenum: opcodenum
|
|
|
|
});
|
|
|
|
i = i + 3;
|
|
|
|
} else {
|
|
|
|
this.chunks.push(opcodenum);
|
|
|
|
i = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.toString = function() {
|
|
|
|
var str = "";
|
|
|
|
|
|
|
|
for (var i = 0; i < this.chunks.length; i++) {
|
|
|
|
var chunk = this.chunks[i];
|
|
|
|
if (typeof chunk === 'number') {
|
|
|
|
var opcodenum = chunk;
|
|
|
|
str = str + Opcode(opcodenum).toString() + " ";
|
|
|
|
} else {
|
|
|
|
var opcodenum = chunk.opcodenum;
|
|
|
|
if (opcodenum === Opcode.map.OP_PUSHDATA1 || opcodenum === Opcode.map.OP_PUSHDATA2 || opcodenum === Opcode.map.OP_PUSHDATA4)
|
|
|
|
str = str + Opcode(opcodenum).toString() + " " ;
|
|
|
|
str = str + chunk.len + " " ;
|
|
|
|
str = str + "0x" + chunk.buf.toString('hex') + " ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return str.substr(0, str.length - 1);
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.isOpReturn = function() {
|
|
|
|
if (this.chunks[0] === Opcode('OP_RETURN').toNumber()
|
|
|
|
&&
|
|
|
|
(this.chunks.length === 1
|
|
|
|
||
|
|
|
|
(this.chunks.length === 2
|
|
|
|
&& this.chunks[1].buf
|
|
|
|
&& this.chunks[1].buf.length <= 40
|
|
|
|
&& this.chunks[1].length === this.chunks.len))) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.isPubkeyhashOut = function() {
|
|
|
|
if (this.chunks[0] === Opcode('OP_DUP').toNumber()
|
|
|
|
&& this.chunks[1] === Opcode('OP_HASH160').toNumber()
|
|
|
|
&& this.chunks[2].buf
|
|
|
|
&& this.chunks[3] === Opcode('OP_EQUALVERIFY').toNumber()
|
|
|
|
&& this.chunks[4] === Opcode('OP_CHECKSIG').toNumber()) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.isPubkeyhashIn = function() {
|
|
|
|
if (this.chunks.length === 2
|
|
|
|
&& this.chunks[0].buf
|
|
|
|
&& this.chunks[1].buf) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Script.prototype.isScripthashOut = function() {
|
|
|
|
if (this.chunks.length === 3
|
|
|
|
&& this.chunks[0] === Opcode('OP_HASH160').toNumber()
|
|
|
|
&& this.chunks[1].buf
|
|
|
|
&& this.chunks[1].buf.length === 20
|
|
|
|
&& this.chunks[2] === Opcode('OP_EQUAL').toNumber()) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//note that these are frequently indistinguishable from pubkeyhashin
|
|
|
|
Script.prototype.isScripthashIn = function() {
|
|
|
|
var allpush = this.chunks.every(function(chunk) {
|
|
|
|
return Buffer.isBuffer(chunk.buf);
|
|
|
|
});
|
|
|
|
if (allpush) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = Script;
|