import * as types from './types' import * as scriptNumber from './script_number' import * as scriptSignature from './script_signature' const bip66 = require('bip66') const ecc = require('tiny-secp256k1') const pushdata = require('pushdata-bitcoin') const typeforce = require('typeforce') export type OpCode = number export const OPS = <{[index:string]: OpCode}> require('bitcoin-ops') const REVERSE_OPS = <{[index:number]: string}> require('bitcoin-ops/map') const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1 function isOPInt (value:number): boolean { return types.Number(value) && ((value === OPS.OP_0) || (value >= OPS.OP_1 && value <= OPS.OP_16) || (value === OPS.OP_1NEGATE)) } function isPushOnlyChunk (value: number | Buffer): boolean { return types.Buffer(value) || isOPInt(value) } export function isPushOnly (value: Array) { return types.Array(value) && value.every(isPushOnlyChunk) } function asMinimalOP (buffer: Buffer): number | void { 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 } function chunksIsBuffer(buf: Buffer | Array): buf is Buffer { return Buffer.isBuffer(buf) } function chunksIsArray(buf: Buffer | Array): buf is Array { return types.Array(buf) } function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { return Buffer.isBuffer(buf) } export function compile (chunks: Buffer | Array): Buffer { // TODO: remove me if (chunksIsBuffer(chunks)) return chunks typeforce(types.Array, chunks) const bufferSize = chunks.reduce(function (accum: number, chunk) { // data chunk if (singleChunkIsBuffer(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 } // opcode return accum + 1 }, 0.0) const buffer = Buffer.allocUnsafe(bufferSize) let offset = 0 chunks.forEach(function (chunk) { // data chunk if (singleChunkIsBuffer(chunk)) { // adhere to BIP62.3, minimal push policy const opcode = asMinimalOP(chunk) if (opcode !== undefined) { buffer.writeUInt8(opcode, offset) offset += 1 return } offset += pushdata.encode(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 } export function decompile (buffer: Buffer | Array): Array | null { // TODO: remove me if (chunksIsArray(buffer)) return buffer typeforce(types.Buffer, buffer) const chunks: Array = [] let i = 0 while (i < buffer.length) { const opcode = buffer[i] // data chunk if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { const d = pushdata.decode(buffer, i) // did reading a pushDataInt fail? if (d === null) return null i += d.size // attempt to read too much data? if (i + d.number > buffer.length) return null const data = buffer.slice(i, i + d.number) i += d.number // decompile minimally const op = asMinimalOP(data) if (op !== undefined) { chunks.push(op) } else { chunks.push(data) } // opcode } else { chunks.push(opcode) i += 1 } } return chunks } export function toASM (chunks: Buffer | Array): string { if (chunksIsBuffer(chunks)) { chunks = >decompile(chunks) } return chunks.map(function (chunk) { // data? if (singleChunkIsBuffer(chunk)) { const op = asMinimalOP(chunk) if (op === undefined) return chunk.toString('hex') chunk = op } // opcode! return REVERSE_OPS[chunk] }).join(' ') } export function fromASM (asm: string): Buffer { typeforce(types.String, asm) return compile(asm.split(' ').map(function (chunkStr) { // opcode? if (OPS[chunkStr] !== undefined) return OPS[chunkStr] typeforce(types.Hex, chunkStr) // data! return Buffer.from(chunkStr, 'hex') })) } export function toStack (chunks: Buffer | Array): Array { chunks = >decompile(chunks) typeforce(isPushOnly, chunks) return chunks.map(function (op) { if (singleChunkIsBuffer(op)) return op if (op === OPS.OP_0) return Buffer.allocUnsafe(0) return scriptNumber.encode(op - OP_INT_BASE) }) } export function isCanonicalPubKey (buffer: Buffer): boolean { return ecc.isPoint(buffer) } export function isDefinedHashType (hashType: number): boolean { const hashTypeMod = hashType & ~0x80 // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE return hashTypeMod > 0x00 && hashTypeMod < 0x04 } export function isCanonicalScriptSignature (buffer: Buffer): boolean { if (!Buffer.isBuffer(buffer)) return false if (!isDefinedHashType(buffer[buffer.length - 1])) return false return bip66.check(buffer.slice(0, -1)) } export const number = scriptNumber export const signature = scriptSignature