2018-12-28 16:53:54 +01:00
|
|
|
import * as types from './types'
|
2018-06-25 08:25:12 +02:00
|
|
|
const Buffer = require('safe-buffer').Buffer
|
|
|
|
const bip66 = require('bip66')
|
2018-06-25 08:24:37 +02:00
|
|
|
const ecc = require('tiny-secp256k1')
|
2018-06-25 08:25:12 +02:00
|
|
|
const pushdata = require('pushdata-bitcoin')
|
|
|
|
const typeforce = require('typeforce')
|
|
|
|
const scriptNumber = require('./script_number')
|
2016-12-17 15:24:00 +01:00
|
|
|
|
2018-06-25 08:25:12 +02:00
|
|
|
const OPS = require('bitcoin-ops')
|
|
|
|
const REVERSE_OPS = require('bitcoin-ops/map')
|
|
|
|
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
2015-10-02 05:00:00 +02:00
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
function isOPInt (value:number): boolean {
|
2016-11-14 05:28:27 +01:00
|
|
|
return types.Number(value) &&
|
2017-03-29 08:05:31 +02:00
|
|
|
((value === OPS.OP_0) ||
|
2016-11-14 05:28:27 +01:00
|
|
|
(value >= OPS.OP_1 && value <= OPS.OP_16) ||
|
2017-03-29 08:05:31 +02:00
|
|
|
(value === OPS.OP_1NEGATE))
|
2016-11-14 05:28:27 +01:00
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
function isPushOnlyChunk (value: number | Buffer): boolean {
|
|
|
|
return types.Buffer(value) || isOPInt(<number>value)
|
2016-11-14 06:03:06 +01:00
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function isPushOnly (value: Array<number | Buffer>) {
|
2016-11-14 06:03:06 +01:00
|
|
|
return types.Array(value) && value.every(isPushOnlyChunk)
|
2016-11-14 05:28:27 +01:00
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
function asMinimalOP (buffer: Buffer): number | void {
|
2017-08-23 09:11:47 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
function chunksIsBuffer(buf: Buffer | Array<number | Buffer>): buf is Buffer {
|
|
|
|
return Buffer.isBuffer(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
function chunksIsArray(buf: Buffer | Array<number | Buffer>): buf is Array<number | Buffer> {
|
|
|
|
return types.Array(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer {
|
|
|
|
return Buffer.isBuffer(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function compile (chunks: Buffer | Array<number | Buffer>): Buffer {
|
2015-08-14 03:16:17 +02:00
|
|
|
// TODO: remove me
|
2018-12-28 04:59:43 +01:00
|
|
|
if (chunksIsBuffer(chunks)) return chunks
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
typeforce(types.Array, chunks)
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
const bufferSize = chunks.reduce(function (accum: number, chunk) {
|
2015-08-14 03:16:17 +02:00
|
|
|
// data chunk
|
2018-12-28 04:59:43 +01:00
|
|
|
if (singleChunkIsBuffer(chunk)) {
|
2016-09-29 05:48:17 +02:00
|
|
|
// adhere to BIP62.3, minimal push policy
|
2017-08-23 09:11:47 +02:00
|
|
|
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
|
2016-09-29 05:48:17 +02:00
|
|
|
return accum + 1
|
|
|
|
}
|
|
|
|
|
2016-12-17 15:24:00 +01:00
|
|
|
return accum + pushdata.encodingLength(chunk.length) + chunk.length
|
2015-08-14 03:16:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// opcode
|
|
|
|
return accum + 1
|
|
|
|
}, 0.0)
|
|
|
|
|
2018-06-25 08:37:45 +02:00
|
|
|
const buffer = Buffer.allocUnsafe(bufferSize)
|
|
|
|
let offset = 0
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
chunks.forEach(function (chunk) {
|
|
|
|
// data chunk
|
2018-12-28 04:59:43 +01:00
|
|
|
if (singleChunkIsBuffer(chunk)) {
|
2016-09-29 05:48:17 +02:00
|
|
|
// adhere to BIP62.3, minimal push policy
|
2018-06-25 08:37:45 +02:00
|
|
|
const opcode = asMinimalOP(chunk)
|
2017-08-23 09:11:47 +02:00
|
|
|
if (opcode !== undefined) {
|
2016-09-29 05:48:17 +02:00
|
|
|
buffer.writeUInt8(opcode, offset)
|
|
|
|
offset += 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-17 15:24:00 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function decompile (buffer: Buffer | Array<number | Buffer>): Array<number | Buffer> {
|
2015-08-14 03:16:17 +02:00
|
|
|
// TODO: remove me
|
2018-12-28 04:59:43 +01:00
|
|
|
if (chunksIsArray(buffer)) return buffer
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
typeforce(types.Buffer, buffer)
|
|
|
|
|
2018-12-28 16:53:54 +01:00
|
|
|
const chunks: Array<number | Buffer> = []
|
2018-06-25 08:37:45 +02:00
|
|
|
let i = 0
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
while (i < buffer.length) {
|
2018-06-25 08:37:45 +02:00
|
|
|
const opcode = buffer[i]
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
// data chunk
|
|
|
|
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) {
|
2018-06-25 08:37:45 +02:00
|
|
|
const d = pushdata.decode(buffer, i)
|
2015-08-14 03:16:17 +02:00
|
|
|
|
2018-06-05 09:24:47 +02:00
|
|
|
// did reading a pushDataInt fail?
|
2018-04-13 17:42:48 +02:00
|
|
|
if (d === null) return null
|
2015-08-14 03:16:17 +02:00
|
|
|
i += d.size
|
|
|
|
|
2018-06-05 09:24:47 +02:00
|
|
|
// attempt to read too much data?
|
2018-04-13 17:42:48 +02:00
|
|
|
if (i + d.number > buffer.length) return null
|
2015-08-14 03:16:17 +02:00
|
|
|
|
2018-06-25 08:37:45 +02:00
|
|
|
const data = buffer.slice(i, i + d.number)
|
2015-08-14 03:16:17 +02:00
|
|
|
i += d.number
|
|
|
|
|
2017-08-23 09:11:47 +02:00
|
|
|
// decompile minimally
|
2018-06-25 08:37:45 +02:00
|
|
|
const op = asMinimalOP(data)
|
2017-08-23 09:11:47 +02:00
|
|
|
if (op !== undefined) {
|
2018-12-28 16:53:54 +01:00
|
|
|
chunks.push(<number>op)
|
2017-08-23 09:11:47 +02:00
|
|
|
} else {
|
|
|
|
chunks.push(data)
|
|
|
|
}
|
2015-08-14 03:16:17 +02:00
|
|
|
|
|
|
|
// opcode
|
|
|
|
} else {
|
|
|
|
chunks.push(opcode)
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks
|
|
|
|
}
|
2014-06-12 13:14:22 +02:00
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function toASM (chunks: Buffer | Array<number | Buffer>): string {
|
|
|
|
if (chunksIsBuffer(chunks)) {
|
2016-09-28 00:44:21 +02:00
|
|
|
chunks = decompile(chunks)
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks.map(function (chunk) {
|
|
|
|
// data?
|
2018-12-28 04:59:43 +01:00
|
|
|
if (singleChunkIsBuffer(chunk)) {
|
2018-06-25 08:37:45 +02:00
|
|
|
const op = asMinimalOP(chunk)
|
2017-08-23 09:11:47 +02:00
|
|
|
if (op === undefined) return chunk.toString('hex')
|
2018-12-28 04:59:43 +01:00
|
|
|
chunk = <number>op
|
2017-08-23 09:11:47 +02:00
|
|
|
}
|
2016-09-28 00:44:21 +02:00
|
|
|
|
|
|
|
// opcode!
|
|
|
|
return REVERSE_OPS[chunk]
|
|
|
|
}).join(' ')
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function fromASM (asm: string): Buffer {
|
2016-09-28 00:44:21 +02:00
|
|
|
typeforce(types.String, asm)
|
|
|
|
|
|
|
|
return compile(asm.split(' ').map(function (chunkStr) {
|
|
|
|
// opcode?
|
|
|
|
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
|
2016-12-17 04:29:42 +01:00
|
|
|
typeforce(types.Hex, chunkStr)
|
2016-09-28 00:44:21 +02:00
|
|
|
|
|
|
|
// data!
|
2017-04-19 09:24:58 +02:00
|
|
|
return Buffer.from(chunkStr, 'hex')
|
2016-09-28 00:44:21 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function toStack (chunks: Buffer | Array<number | Buffer>): Array<Buffer> {
|
2016-12-14 05:52:15 +01:00
|
|
|
chunks = decompile(chunks)
|
2016-11-14 06:03:06 +01:00
|
|
|
typeforce(isPushOnly, chunks)
|
2016-11-14 01:17:27 +01:00
|
|
|
|
2016-11-14 05:28:27 +01:00
|
|
|
return chunks.map(function (op) {
|
2018-12-28 04:59:43 +01:00
|
|
|
if (singleChunkIsBuffer(op)) return op
|
2017-04-19 09:24:58 +02:00
|
|
|
if (op === OPS.OP_0) return Buffer.allocUnsafe(0)
|
2016-11-14 05:28:27 +01:00
|
|
|
|
|
|
|
return scriptNumber.encode(op - OP_INT_BASE)
|
2016-11-14 01:17:27 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function isCanonicalPubKey (buffer: Buffer): boolean {
|
2018-06-05 09:15:53 +02:00
|
|
|
return ecc.isPoint(buffer)
|
2014-06-24 09:32:23 +02:00
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function isDefinedHashType (hashType: number): boolean {
|
2018-06-25 08:37:45 +02:00
|
|
|
const hashTypeMod = hashType & ~0x80
|
2015-08-22 04:31:00 +02:00
|
|
|
|
2018-05-29 03:37:03 +02:00
|
|
|
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
2015-08-22 04:31:00 +02:00
|
|
|
return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export function isCanonicalScriptSignature (buffer: Buffer): boolean {
|
2016-09-28 00:44:21 +02:00
|
|
|
if (!Buffer.isBuffer(buffer)) return false
|
|
|
|
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
|
|
|
|
|
|
|
return bip66.check(buffer.slice(0, -1))
|
|
|
|
}
|
|
|
|
|
2018-12-28 04:59:43 +01:00
|
|
|
export const number = require('./script_number')
|
|
|
|
export const signature = require('./script_signature')
|