177 lines
5.2 KiB
JavaScript
177 lines
5.2 KiB
JavaScript
'use strict';
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
const scriptNumber = require('./script_number');
|
|
const scriptSignature = require('./script_signature');
|
|
const types = require('./types');
|
|
const bip66 = require('bip66');
|
|
const ecc = require('tiny-secp256k1');
|
|
const pushdata = require('pushdata-bitcoin');
|
|
const typeforce = require('typeforce');
|
|
exports.OPS = require('bitcoin-ops');
|
|
const REVERSE_OPS = require('bitcoin-ops/map');
|
|
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1
|
|
function isOPInt(value) {
|
|
return (
|
|
types.Number(value) &&
|
|
(value === exports.OPS.OP_0 ||
|
|
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) ||
|
|
value === exports.OPS.OP_1NEGATE)
|
|
);
|
|
}
|
|
function isPushOnlyChunk(value) {
|
|
return types.Buffer(value) || isOPInt(value);
|
|
}
|
|
function isPushOnly(value) {
|
|
return types.Array(value) && value.every(isPushOnlyChunk);
|
|
}
|
|
exports.isPushOnly = isPushOnly;
|
|
function asMinimalOP(buffer) {
|
|
if (buffer.length === 0) return exports.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 exports.OPS.OP_1NEGATE;
|
|
}
|
|
function chunksIsBuffer(buf) {
|
|
return Buffer.isBuffer(buf);
|
|
}
|
|
function chunksIsArray(buf) {
|
|
return types.Array(buf);
|
|
}
|
|
function singleChunkIsBuffer(buf) {
|
|
return Buffer.isBuffer(buf);
|
|
}
|
|
function compile(chunks) {
|
|
// TODO: remove me
|
|
if (chunksIsBuffer(chunks)) return chunks;
|
|
typeforce(types.Array, chunks);
|
|
const bufferSize = chunks.reduce((accum, 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(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;
|
|
}
|
|
exports.compile = compile;
|
|
function decompile(buffer) {
|
|
// TODO: remove me
|
|
if (chunksIsArray(buffer)) return buffer;
|
|
typeforce(types.Buffer, buffer);
|
|
const chunks = [];
|
|
let i = 0;
|
|
while (i < buffer.length) {
|
|
const opcode = buffer[i];
|
|
// data chunk
|
|
if (opcode > exports.OPS.OP_0 && opcode <= exports.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;
|
|
}
|
|
exports.decompile = decompile;
|
|
function toASM(chunks) {
|
|
if (chunksIsBuffer(chunks)) {
|
|
chunks = decompile(chunks);
|
|
}
|
|
return chunks
|
|
.map(chunk => {
|
|
// data?
|
|
if (singleChunkIsBuffer(chunk)) {
|
|
const op = asMinimalOP(chunk);
|
|
if (op === undefined) return chunk.toString('hex');
|
|
chunk = op;
|
|
}
|
|
// opcode!
|
|
return REVERSE_OPS[chunk];
|
|
})
|
|
.join(' ');
|
|
}
|
|
exports.toASM = toASM;
|
|
function fromASM(asm) {
|
|
typeforce(types.String, asm);
|
|
return compile(
|
|
asm.split(' ').map(chunkStr => {
|
|
// opcode?
|
|
if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr];
|
|
typeforce(types.Hex, chunkStr);
|
|
// data!
|
|
return Buffer.from(chunkStr, 'hex');
|
|
}),
|
|
);
|
|
}
|
|
exports.fromASM = fromASM;
|
|
function toStack(chunks) {
|
|
chunks = decompile(chunks);
|
|
typeforce(isPushOnly, chunks);
|
|
return chunks.map(op => {
|
|
if (singleChunkIsBuffer(op)) return op;
|
|
if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0);
|
|
return scriptNumber.encode(op - OP_INT_BASE);
|
|
});
|
|
}
|
|
exports.toStack = toStack;
|
|
function isCanonicalPubKey(buffer) {
|
|
return ecc.isPoint(buffer);
|
|
}
|
|
exports.isCanonicalPubKey = isCanonicalPubKey;
|
|
function isDefinedHashType(hashType) {
|
|
const hashTypeMod = hashType & ~0x80;
|
|
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
|
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
|
|
}
|
|
exports.isDefinedHashType = isDefinedHashType;
|
|
function isCanonicalScriptSignature(buffer) {
|
|
if (!Buffer.isBuffer(buffer)) return false;
|
|
if (!isDefinedHashType(buffer[buffer.length - 1])) return false;
|
|
return bip66.check(buffer.slice(0, -1));
|
|
}
|
|
exports.isCanonicalScriptSignature = isCanonicalScriptSignature;
|
|
// tslint:disable-next-line variable-name
|
|
exports.number = scriptNumber;
|
|
exports.signature = scriptSignature;
|