2015-08-22 03:54:00 +02:00
|
|
|
var bip66 = require('bip66')
|
2015-08-14 03:16:17 +02:00
|
|
|
var bufferutils = require('./bufferutils')
|
2015-08-11 09:01:47 +02:00
|
|
|
var typeforce = require('typeforce')
|
|
|
|
var types = require('./types')
|
2016-11-14 01:17:27 +01:00
|
|
|
var scriptNumber = require('./script_number')
|
2016-06-09 00:00:37 +02:00
|
|
|
var OPS = require('./opcodes.json')
|
2015-10-02 04:59:30 +02:00
|
|
|
var REVERSE_OPS = (function () {
|
|
|
|
var result = {}
|
|
|
|
for (var op in OPS) {
|
|
|
|
var code = OPS[op]
|
|
|
|
result[code] = op
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
})()
|
2015-11-19 07:04:21 +01:00
|
|
|
var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
2015-10-02 05:00:00 +02:00
|
|
|
|
2015-08-14 03:16:17 +02:00
|
|
|
function compile (chunks) {
|
|
|
|
// TODO: remove me
|
2015-11-26 02:07:04 +01:00
|
|
|
if (Buffer.isBuffer(chunks)) return chunks
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
typeforce(types.Array, chunks)
|
|
|
|
|
|
|
|
var bufferSize = chunks.reduce(function (accum, chunk) {
|
|
|
|
// data chunk
|
|
|
|
if (Buffer.isBuffer(chunk)) {
|
2016-09-29 05:48:17 +02:00
|
|
|
// adhere to BIP62.3, minimal push policy
|
|
|
|
if (chunk.length === 1 && chunk[0] >= 1 && chunk[0] <= 16) {
|
|
|
|
return accum + 1
|
|
|
|
}
|
|
|
|
|
2015-08-14 03:16:17 +02:00
|
|
|
return accum + bufferutils.pushDataSize(chunk.length) + chunk.length
|
|
|
|
}
|
|
|
|
|
|
|
|
// opcode
|
|
|
|
return accum + 1
|
|
|
|
}, 0.0)
|
|
|
|
|
|
|
|
var buffer = new Buffer(bufferSize)
|
|
|
|
var offset = 0
|
|
|
|
|
|
|
|
chunks.forEach(function (chunk) {
|
|
|
|
// data chunk
|
|
|
|
if (Buffer.isBuffer(chunk)) {
|
2016-09-29 05:48:17 +02:00
|
|
|
// adhere to BIP62.3, minimal push policy
|
|
|
|
if (chunk.length === 1 && chunk[0] >= 1 && chunk[0] <= 16) {
|
|
|
|
var opcode = OP_INT_BASE + chunk[0]
|
|
|
|
buffer.writeUInt8(opcode, offset)
|
|
|
|
offset += 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-08-14 03:16:17 +02:00
|
|
|
offset += bufferutils.writePushDataInt(buffer, chunk.length, offset)
|
|
|
|
|
|
|
|
chunk.copy(buffer, offset)
|
|
|
|
offset += chunk.length
|
|
|
|
|
|
|
|
// opcode
|
|
|
|
} else {
|
|
|
|
buffer.writeUInt8(chunk, offset)
|
|
|
|
offset += 1
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (offset !== buffer.length) throw new Error('Could not decode chunks')
|
|
|
|
return buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
function decompile (buffer) {
|
|
|
|
// TODO: remove me
|
|
|
|
if (types.Array(buffer)) return buffer
|
|
|
|
|
|
|
|
typeforce(types.Buffer, buffer)
|
|
|
|
|
|
|
|
var chunks = []
|
|
|
|
var i = 0
|
|
|
|
|
|
|
|
while (i < buffer.length) {
|
2015-08-25 10:08:32 +02:00
|
|
|
var opcode = buffer[i]
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
// data chunk
|
|
|
|
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) {
|
|
|
|
var d = bufferutils.readPushDataInt(buffer, i)
|
|
|
|
|
|
|
|
// did reading a pushDataInt fail? empty script
|
|
|
|
if (d === null) return []
|
|
|
|
i += d.size
|
|
|
|
|
|
|
|
// attempt to read too much data? empty script
|
|
|
|
if (i + d.number > buffer.length) return []
|
|
|
|
|
|
|
|
var data = buffer.slice(i, i + d.number)
|
|
|
|
i += d.number
|
|
|
|
|
|
|
|
chunks.push(data)
|
|
|
|
|
|
|
|
// opcode
|
|
|
|
} else {
|
|
|
|
chunks.push(opcode)
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks
|
|
|
|
}
|
2014-06-12 13:14:22 +02:00
|
|
|
|
2016-09-28 00:44:21 +02:00
|
|
|
function toASM (chunks) {
|
|
|
|
if (Buffer.isBuffer(chunks)) {
|
|
|
|
chunks = decompile(chunks)
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks.map(function (chunk) {
|
|
|
|
// data?
|
|
|
|
if (Buffer.isBuffer(chunk)) return chunk.toString('hex')
|
|
|
|
|
|
|
|
// opcode!
|
|
|
|
return REVERSE_OPS[chunk]
|
|
|
|
}).join(' ')
|
|
|
|
}
|
|
|
|
|
|
|
|
function fromASM (asm) {
|
|
|
|
typeforce(types.String, asm)
|
|
|
|
|
|
|
|
return compile(asm.split(' ').map(function (chunkStr) {
|
|
|
|
// opcode?
|
|
|
|
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
|
|
|
|
|
|
|
|
// data!
|
|
|
|
return new Buffer(chunkStr, 'hex')
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2016-11-14 01:17:27 +01:00
|
|
|
function decompilePushOnly (script) {
|
|
|
|
return decompile(script).map(function (op) {
|
|
|
|
if (op instanceof Buffer) {
|
|
|
|
return op
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op === OPS.OP_0) {
|
|
|
|
return new Buffer(0)
|
|
|
|
} else if (op === OPS.OP_1NEGATE || op >= OPS.OP_1 && op <= OPS.OP_16) {
|
|
|
|
return scriptNumber.encode(op - OP_INT_BASE)
|
|
|
|
} else {
|
|
|
|
throw new Error('Can only evaluate push-only opcodes')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function compilePushOnly (set) {
|
|
|
|
return compile(set.map(function (op) {
|
|
|
|
if (op.length === 0) {
|
|
|
|
return OPS.OP_0
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op.length === 1) {
|
|
|
|
if (op[0] === 0x81) {
|
|
|
|
return OPS.OP_1NEGATE
|
|
|
|
} else if (op[0] >= 1 && op[0] <= OPS.OP_16) {
|
|
|
|
return op[0] + OP_INT_BASE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return op
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2015-02-23 00:36:57 +01:00
|
|
|
function isCanonicalPubKey (buffer) {
|
2014-06-24 09:32:23 +02:00
|
|
|
if (!Buffer.isBuffer(buffer)) return false
|
2015-08-22 04:31:32 +02:00
|
|
|
if (buffer.length < 33) return false
|
|
|
|
|
|
|
|
switch (buffer[0]) {
|
|
|
|
case 0x02:
|
|
|
|
case 0x03:
|
|
|
|
return buffer.length === 33
|
|
|
|
case 0x04:
|
|
|
|
return buffer.length === 65
|
2014-06-24 09:32:23 +02:00
|
|
|
}
|
|
|
|
|
2015-08-22 04:31:32 +02:00
|
|
|
return false
|
2014-06-24 09:32:23 +02:00
|
|
|
}
|
|
|
|
|
2015-08-22 04:31:00 +02:00
|
|
|
function isDefinedHashType (hashType) {
|
|
|
|
var hashTypeMod = hashType & ~0x80
|
|
|
|
|
|
|
|
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
|
|
|
return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
|
|
|
}
|
|
|
|
|
2016-09-28 00:44:21 +02:00
|
|
|
function isCanonicalSignature (buffer) {
|
|
|
|
if (!Buffer.isBuffer(buffer)) return false
|
|
|
|
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
|
|
|
|
|
|
|
return bip66.check(buffer.slice(0, -1))
|
|
|
|
}
|
|
|
|
|
2016-11-02 02:30:37 +01:00
|
|
|
module.exports = {
|
|
|
|
compile: compile,
|
|
|
|
decompile: decompile,
|
|
|
|
fromASM: fromASM,
|
|
|
|
toASM: toASM,
|
2016-11-14 01:17:27 +01:00
|
|
|
compilePushOnly: compilePushOnly,
|
|
|
|
decompilePushOnly: decompilePushOnly,
|
2016-11-02 02:30:37 +01:00
|
|
|
number: require('./script_number'),
|
2014-06-24 09:32:23 +02:00
|
|
|
|
2016-11-02 02:30:37 +01:00
|
|
|
isCanonicalPubKey: isCanonicalPubKey,
|
|
|
|
isCanonicalSignature: isCanonicalSignature,
|
|
|
|
isDefinedHashType: isDefinedHashType
|
2016-11-01 16:06:01 +01:00
|
|
|
}
|
2016-07-11 17:37:59 +02:00
|
|
|
|
2016-11-02 04:11:55 +01:00
|
|
|
var templates = require('./templates')
|
|
|
|
for (var key in templates) {
|
|
|
|
module.exports[key] = templates[key]
|
2014-10-13 09:38:54 +02:00
|
|
|
}
|