bitcoinjs-lib/ts_src/script.ts

217 lines
5.4 KiB
TypeScript
Raw Permalink Normal View History

2019-03-07 05:17:03 +01:00
import { Stack } from './payments';
2019-03-03 15:07:49 +01:00
import * as scriptNumber from './script_number';
import * as scriptSignature from './script_signature';
2019-03-07 05:17:03 +01:00
import * as types from './types';
2019-03-03 15:07:49 +01:00
const bip66 = require('bip66');
const ecc = require('tiny-secp256k1');
const pushdata = require('pushdata-bitcoin');
const typeforce = require('typeforce');
export type OpCode = number;
2019-03-07 05:17:03 +01:00
export const OPS = require('bitcoin-ops') as { [index: string]: OpCode };
2019-03-03 15:07:49 +01:00
2019-03-07 05:17:03 +01:00
const REVERSE_OPS = require('bitcoin-ops/map') as { [index: number]: string };
2019-03-03 15:07:49 +01:00
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)
);
}
2019-03-03 15:07:49 +01:00
function isPushOnlyChunk(value: number | Buffer): boolean {
2019-03-07 05:17:03 +01:00
return types.Buffer(value) || isOPInt(value as number);
2016-11-14 06:03:06 +01:00
}
export function isPushOnly(value: Stack): boolean {
2019-03-03 15:07:49 +01:00
return types.Array(value) && value.every(isPushOnlyChunk);
}
2019-03-03 15:07:49 +01:00
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;
}
2019-03-07 05:17:03 +01:00
function chunksIsBuffer(buf: Buffer | Stack): buf is Buffer {
2019-03-03 15:07:49 +01:00
return Buffer.isBuffer(buf);
}
2019-03-07 05:17:03 +01:00
function chunksIsArray(buf: Buffer | Stack): buf is Stack {
2019-03-03 15:07:49 +01:00
return types.Array(buf);
}
function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer {
2019-03-03 15:07:49 +01:00
return Buffer.isBuffer(buf);
}
2019-03-07 05:17:03 +01:00
export function compile(chunks: Buffer | Stack): Buffer {
2015-08-14 03:16:17 +02:00
// TODO: remove me
2019-03-03 15:07:49 +01:00
if (chunksIsBuffer(chunks)) return chunks;
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
typeforce(types.Array, chunks);
2015-08-14 03:16:17 +02:00
2019-03-07 05:17:03 +01:00
const bufferSize = chunks.reduce((accum: number, chunk) => {
2015-08-14 03:16:17 +02:00
// data chunk
if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
2019-03-03 15:07:49 +01:00
return accum + 1;
}
2019-03-03 15:07:49 +01:00
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
2015-08-14 03:16:17 +02:00
}
// opcode
2019-03-03 15:07:49 +01:00
return accum + 1;
}, 0.0);
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
const buffer = Buffer.allocUnsafe(bufferSize);
let offset = 0;
2015-08-14 03:16:17 +02:00
2019-03-07 05:17:03 +01:00
chunks.forEach(chunk => {
2015-08-14 03:16:17 +02:00
// data chunk
if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
2019-03-03 15:07:49 +01:00
const opcode = asMinimalOP(chunk);
if (opcode !== undefined) {
2019-03-03 15:07:49 +01:00
buffer.writeUInt8(opcode, offset);
offset += 1;
return;
}
2019-03-03 15:07:49 +01:00
offset += pushdata.encode(buffer, chunk.length, offset);
chunk.copy(buffer, offset);
offset += chunk.length;
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
// opcode
2015-08-14 03:16:17 +02:00
} else {
2019-03-03 15:07:49 +01:00
buffer.writeUInt8(chunk, offset);
offset += 1;
2015-08-14 03:16:17 +02:00
}
2019-03-03 15:07:49 +01:00
});
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
if (offset !== buffer.length) throw new Error('Could not decode chunks');
return buffer;
2015-08-14 03:16:17 +02:00
}
2019-03-03 15:07:49 +01:00
export function decompile(
buffer: Buffer | Array<number | Buffer>,
): Array<number | Buffer> | null {
2015-08-14 03:16:17 +02:00
// TODO: remove me
2019-03-03 15:07:49 +01:00
if (chunksIsArray(buffer)) return buffer;
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
typeforce(types.Buffer, buffer);
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
const chunks: Array<number | Buffer> = [];
let i = 0;
2015-08-14 03:16:17 +02:00
while (i < buffer.length) {
2019-03-03 15:07:49 +01:00
const opcode = buffer[i];
2015-08-14 03:16:17 +02:00
// data chunk
2019-03-03 15:07:49 +01:00
if (opcode > OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) {
const d = pushdata.decode(buffer, i);
2015-08-14 03:16:17 +02:00
// did reading a pushDataInt fail?
2019-03-03 15:07:49 +01:00
if (d === null) return null;
i += d.size;
2015-08-14 03:16:17 +02:00
// attempt to read too much data?
2019-03-03 15:07:49 +01:00
if (i + d.number > buffer.length) return null;
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
const data = buffer.slice(i, i + d.number);
i += d.number;
2015-08-14 03:16:17 +02:00
// decompile minimally
2019-03-03 15:07:49 +01:00
const op = asMinimalOP(data);
if (op !== undefined) {
2019-03-03 15:07:49 +01:00
chunks.push(op);
} else {
2019-03-03 15:07:49 +01:00
chunks.push(data);
}
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
// opcode
2015-08-14 03:16:17 +02:00
} else {
2019-03-03 15:07:49 +01:00
chunks.push(opcode);
2015-08-14 03:16:17 +02:00
2019-03-03 15:07:49 +01:00
i += 1;
2015-08-14 03:16:17 +02:00
}
}
2019-03-03 15:07:49 +01:00
return chunks;
2015-08-14 03:16:17 +02:00
}
2019-03-03 15:07:49 +01:00
export function toASM(chunks: Buffer | Array<number | Buffer>): string {
if (chunksIsBuffer(chunks)) {
2019-03-07 05:17:03 +01:00
chunks = decompile(chunks) as Stack;
}
2019-03-03 15:07:49 +01:00
return chunks
2019-03-07 05:17:03 +01:00
.map(chunk => {
2019-03-03 15:07:49 +01:00
// data?
if (singleChunkIsBuffer(chunk)) {
const op = asMinimalOP(chunk);
if (op === undefined) return chunk.toString('hex');
2019-03-07 05:17:03 +01:00
chunk = op as number;
2019-03-03 15:07:49 +01:00
}
2019-03-03 15:07:49 +01:00
// opcode!
return REVERSE_OPS[chunk];
})
.join(' ');
}
2019-03-03 15:07:49 +01:00
export function fromASM(asm: string): Buffer {
typeforce(types.String, asm);
2019-03-03 15:07:49 +01:00
return compile(
2019-03-07 05:17:03 +01:00
asm.split(' ').map(chunkStr => {
2019-03-03 15:07:49 +01:00
// opcode?
if (OPS[chunkStr] !== undefined) return OPS[chunkStr];
typeforce(types.Hex, chunkStr);
2019-03-03 15:07:49 +01:00
// data!
return Buffer.from(chunkStr, 'hex');
}),
);
}
2019-03-07 05:17:03 +01:00
export function toStack(chunks: Buffer | Array<number | Buffer>): Buffer[] {
chunks = decompile(chunks) as Stack;
2019-03-03 15:07:49 +01:00
typeforce(isPushOnly, chunks);
2019-03-07 05:17:03 +01:00
return chunks.map(op => {
2019-03-03 15:07:49 +01:00
if (singleChunkIsBuffer(op)) return op;
if (op === OPS.OP_0) return Buffer.allocUnsafe(0);
2019-03-03 15:07:49 +01:00
return scriptNumber.encode(op - OP_INT_BASE);
});
}
2019-03-03 15:07:49 +01:00
export function isCanonicalPubKey(buffer: Buffer): boolean {
return ecc.isPoint(buffer);
2014-06-24 09:32:23 +02:00
}
2019-03-03 15:07:49 +01:00
export function isDefinedHashType(hashType: number): boolean {
const hashTypeMod = hashType & ~0x80;
2018-05-29 03:37:03 +02:00
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
2019-03-03 15:07:49 +01:00
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
}
2019-03-03 15:07:49 +01:00
export function isCanonicalScriptSignature(buffer: Buffer): boolean {
if (!Buffer.isBuffer(buffer)) return false;
if (!isDefinedHashType(buffer[buffer.length - 1])) return false;
2019-03-03 15:07:49 +01:00
return bip66.check(buffer.slice(0, -1));
}
2019-03-07 05:17:03 +01:00
// tslint:disable-next-line variable-name
2019-03-03 15:07:49 +01:00
export const number = scriptNumber;
export const signature = scriptSignature;