@ecash/lib
Version:
Library for eCash transaction building
142 lines • 4.84 kB
JavaScript
;
// Copyright (c) 2024 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
Object.defineProperty(exports, "__esModule", { value: true });
exports.pushNumberOp = exports.pushBytesOp = exports.writeOp = exports.readOp = exports.isPushOp = void 0;
const opcode_js_1 = require("./opcode.js");
/** Returns true if the given object is a `PushOp` */
function isPushOp(op) {
if (!op || typeof op !== 'object') {
return false;
}
if (!op.hasOwnProperty('opcode') || !op.hasOwnProperty('data')) {
return false;
}
return typeof op.opcode === 'number' && op.data instanceof Uint8Array;
}
exports.isPushOp = isPushOp;
/** Read a single Script operation from the bytes */
function readOp(bytes) {
const opcode = bytes.readU8();
let numBytes;
switch (opcode) {
case opcode_js_1.OP_PUSHDATA1:
numBytes = bytes.readU8();
break;
case opcode_js_1.OP_PUSHDATA2:
numBytes = bytes.readU16();
break;
case opcode_js_1.OP_PUSHDATA4:
numBytes = bytes.readU32();
break;
default:
if (opcode < 0x01 || opcode > 0x4b) {
// Non-push opcode
return opcode;
}
numBytes = opcode;
}
const data = bytes.readBytes(numBytes);
return { opcode, data };
}
exports.readOp = readOp;
/** Write a Script operation to the writer */
function writeOp(op, writer) {
if (typeof op == 'number') {
writer.putU8(op);
return;
}
if (!isPushOp(op)) {
throw `Unexpected op: ${op}`;
}
writer.putU8(op.opcode);
switch (op.opcode) {
case opcode_js_1.OP_PUSHDATA1:
writer.putU8(op.data.length);
break;
case opcode_js_1.OP_PUSHDATA2:
writer.putU16(op.data.length);
break;
case opcode_js_1.OP_PUSHDATA4:
writer.putU32(op.data.length);
break;
default:
if (op.opcode < 0 || op.opcode > 0x4b) {
throw `Not a pushop opcode: 0x${op.opcode.toString(16)}`;
}
if (op.opcode != op.data.length) {
throw (`Inconsistent PushOp, claims to push ${op.opcode} bytes ` +
`but actually has ${op.data.length} bytes attached`);
}
}
writer.putBytes(op.data);
}
exports.writeOp = writeOp;
/** Return an Op that minimally pushes the given bytes onto the stack */
function pushBytesOp(data) {
if (data.length == 0) {
return opcode_js_1.OP_0;
}
else if (data.length == 1) {
if (data[0] >= 1 && data[0] <= 16) {
return data[0] + 0x50;
}
else if (data[0] == 0x81) {
return opcode_js_1.OP_1NEGATE;
}
}
let opcode;
if (data.length >= 0x01 && data.length <= 0x4b) {
opcode = data.length;
}
else if (data.length >= 0x4c && data.length <= 0xff) {
opcode = opcode_js_1.OP_PUSHDATA1;
}
else if (data.length >= 0x100 && data.length <= 0xffff) {
opcode = opcode_js_1.OP_PUSHDATA2;
}
else if (data.length >= 0x10000 && data.length <= 0xffffffff) {
opcode = opcode_js_1.OP_PUSHDATA4;
}
else {
throw 'Bytes way too large';
}
return { opcode, data };
}
exports.pushBytesOp = pushBytesOp;
/**
* Returns an Op that pushes the minimally encoded byte representation of a
* number to the stack. The bytes pushed to the stack can be used directly
* without the need for OP_BIN2NUM.
*/
function pushNumberOp(value) {
if (value == 0) {
return opcode_js_1.OP_0;
}
// Prepare number for encoding. The algorithm below replicates the one used
// in `src/script/script.h` intentionally to avoid discrepancies.
const auxValue = BigInt(value);
let bytes = [];
let negative = auxValue < 0;
let absvalue = negative ? ~auxValue + 1n : auxValue;
// Encode value in little endian byte order by iteratively pushing the
// least significant byte until shifting right 1 more byte produces 0
while (absvalue) {
bytes.push(Number(absvalue & 0xffn));
absvalue >>= 8n;
}
// The MSB will encode the sign which means that, if the previous encoding
// of the absolute value uses that bit, a new byte must be added to encode
// the sign. If bit is not set, then it must be set for negative numbers.
let last = bytes[bytes.length - 1];
if (last & 0x80) {
bytes.push(negative ? 0x80 : 0x00);
}
else if (negative) {
bytes[bytes.length - 1] = last | 0x80;
}
return pushBytesOp(Uint8Array.from(bytes));
}
exports.pushNumberOp = pushNumberOp;
//# sourceMappingURL=op.js.map