bitcoinjs-lib/src/script.js

216 lines
4.9 KiB
JavaScript
Raw Normal View History

2017-05-23 07:14:08 +02:00
var Buffer = require('safe-buffer').Buffer
var bip66 = require('bip66')
var pushdata = require('pushdata-bitcoin')
2015-08-11 09:01:47 +02:00
var typeforce = require('typeforce')
var types = require('./types')
var scriptNumber = require('./script_number')
var OPS = require('bitcoin-ops')
var REVERSE_OPS = require('bitcoin-ops/map')
var OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
function isOPInt (value) {
return types.Number(value) &&
((value === OPS.OP_0) ||
(value >= OPS.OP_1 && value <= OPS.OP_16) ||
(value === OPS.OP_1NEGATE))
}
2016-11-14 06:03:06 +01:00
function isPushOnlyChunk (value) {
return types.Buffer(value) || isOPInt(value)
}
function isPushOnly (value) {
return types.Array(value) && value.every(isPushOnlyChunk)
}
function asMinimalOP (buffer) {
if (buffer.length === 0) return OPS.OP_0
if (buffer.length !== 1) return
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]
if (buffer[0] === 0x81) return OPS.OP_1NEGATE
}
2015-08-14 03:16:17 +02:00
function compile (chunks) {
// TODO: remove me
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)) {
// adhere to BIP62.3, minimal push policy
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
return accum + 1
}
return accum + pushdata.encodingLength(chunk.length) + chunk.length
2015-08-14 03:16:17 +02:00
}
// opcode
return accum + 1
}, 0.0)
2017-04-19 09:24:58 +02:00
var buffer = Buffer.allocUnsafe(bufferSize)
2015-08-14 03:16:17 +02:00
var offset = 0
chunks.forEach(function (chunk) {
// data chunk
if (Buffer.isBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
var opcode = asMinimalOP(chunk)
if (opcode !== undefined) {
buffer.writeUInt8(opcode, offset)
offset += 1
return
}
offset += pushdata.encode(buffer, chunk.length, offset)
2015-08-14 03:16:17 +02:00
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 = pushdata.decode(buffer, i)
2015-08-14 03:16:17 +02:00
// did reading a pushDataInt fail? empty script
if (d === null) return null
2015-08-14 03:16:17 +02:00
i += d.size
// attempt to read too much data? empty script
if (i + d.number > buffer.length) return null
2015-08-14 03:16:17 +02:00
var data = buffer.slice(i, i + d.number)
i += d.number
// decompile minimally
var op = asMinimalOP(data)
if (op !== undefined) {
chunks.push(op)
} else {
chunks.push(data)
}
2015-08-14 03:16:17 +02:00
// opcode
} else {
chunks.push(opcode)
i += 1
}
}
return chunks
}
function toASM (chunks) {
if (Buffer.isBuffer(chunks)) {
chunks = decompile(chunks)
}
return chunks.map(function (chunk) {
// data?
if (Buffer.isBuffer(chunk)) {
var op = asMinimalOP(chunk)
if (op === undefined) return chunk.toString('hex')
chunk = op
}
// 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]
typeforce(types.Hex, chunkStr)
// data!
2017-04-19 09:24:58 +02:00
return Buffer.from(chunkStr, 'hex')
}))
}
function toStack (chunks) {
chunks = decompile(chunks)
2016-11-14 06:03:06 +01:00
typeforce(isPushOnly, chunks)
return chunks.map(function (op) {
if (Buffer.isBuffer(op)) return op
2017-04-19 09:24:58 +02:00
if (op === OPS.OP_0) return Buffer.allocUnsafe(0)
return scriptNumber.encode(op - OP_INT_BASE)
})
}
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
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
}
return false
2014-06-24 09:32:23 +02:00
}
function isDefinedHashType (hashType) {
var hashTypeMod = hashType & ~0x80
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
return hashTypeMod > 0x00 && hashTypeMod < 0x04
}
function isCanonicalSignature (buffer) {
if (!Buffer.isBuffer(buffer)) return false
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
return bip66.check(buffer.slice(0, -1))
}
module.exports = {
compile: compile,
decompile: decompile,
fromASM: fromASM,
toASM: toASM,
toStack: toStack,
number: require('./script_number'),
signature: require('./script_signature'),
2014-06-24 09:32:23 +02:00
isCanonicalPubKey: isCanonicalPubKey,
isCanonicalSignature: isCanonicalSignature,
isPushOnly: isPushOnly,
isDefinedHashType: isDefinedHashType
}