Skip to content

Commit

Permalink
build and test
Browse files Browse the repository at this point in the history
  • Loading branch information
ethsmartcoder committed Dec 14, 2024
1 parent 9f9bcbc commit 3d53450
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 35 deletions.
108 changes: 91 additions & 17 deletions src/cjs/script.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
'use strict';
/**
* Script tools module for working with Bitcoin scripts.
* Provides utilities such as decompiling, compiling, converting to/from ASM, stack manipulation,
* and script validation functions.
*
* @packageDocumentation
*/
var __createBinding =
(this && this.__createBinding) ||
(Object.create
Expand Down Expand Up @@ -55,10 +62,6 @@ exports.toStack = toStack;
exports.isCanonicalPubKey = isCanonicalPubKey;
exports.isDefinedHashType = isDefinedHashType;
exports.isCanonicalScriptSignature = isCanonicalScriptSignature;
/**
* Script tools, including decompile, compile, toASM, fromASM, toStack, isCanonicalPubKey, isCanonicalScriptSignature
* @packageDocumentation
*/
const bip66 = __importStar(require('./bip66.cjs'));
const ops_js_1 = require('./ops.cjs');
Object.defineProperty(exports, 'OPS', {
Expand All @@ -73,8 +76,16 @@ const scriptSignature = __importStar(require('./script_signature.cjs'));
const types = __importStar(require('./types.cjs'));
const tools = __importStar(require('uint8array-tools'));
const v = __importStar(require('valibot'));
/** Base opcode for OP_INT values. */
const OP_INT_BASE = ops_js_1.OPS.OP_RESERVED; // OP_1 - 1
/** Validation schema for a Bitcoin script stack. */
const StackSchema = v.array(v.union([v.instance(Uint8Array), v.number()]));
/**
* Determines if a value corresponds to an OP_INT opcode.
*
* @param value - The opcode to check.
* @returns True if the value is an OP_INT, false otherwise.
*/
function isOPInt(value) {
return (
v.is(v.number(), value) &&
Expand All @@ -83,57 +94,95 @@ function isOPInt(value) {
value === ops_js_1.OPS.OP_1NEGATE)
);
}
/**
* Checks if a script chunk is push-only (contains only data or OP_INT opcodes).
*
* @param value - The chunk to check.
* @returns True if the chunk is push-only, false otherwise.
*/
function isPushOnlyChunk(value) {
return v.is(types.BufferSchema, value) || isOPInt(value);
}
/**
* Determines if a stack consists of only push operations.
*
* @param value - The stack to check.
* @returns True if all elements in the stack are push-only, false otherwise.
*/
function isPushOnly(value) {
return v.is(v.pipe(v.any(), v.everyItem(isPushOnlyChunk)), value);
}
/**
* Counts the number of non-push-only opcodes in a stack.
*
* @param value - The stack to analyze.
* @returns The count of non-push-only opcodes.
*/
function countNonPushOnlyOPs(value) {
return value.length - value.filter(isPushOnlyChunk).length;
}
/**
* Converts a minimal script buffer to its corresponding opcode, if applicable.
*
* @param buffer - The buffer to check.
* @returns The corresponding opcode or undefined if not minimal.
*/
function asMinimalOP(buffer) {
if (buffer.length === 0) return ops_js_1.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_js_1.OPS.OP_1NEGATE;
}
/**
* Determines if a buffer or stack is a Uint8Array.
*
* @param buf - The buffer or stack to check.
* @returns True if the input is a Uint8Array, false otherwise.
*/
function chunksIsBuffer(buf) {
return buf instanceof Uint8Array;
}
/**
* Determines if a buffer or stack is a valid stack.
*
* @param buf - The buffer or stack to check.
* @returns True if the input is a stack, false otherwise.
*/
function chunksIsArray(buf) {
return v.is(StackSchema, buf);
}
/**
* Determines if a single chunk is a Uint8Array.
*
* @param buf - The chunk to check.
* @returns True if the chunk is a Uint8Array, false otherwise.
*/
function singleChunkIsBuffer(buf) {
return buf instanceof Uint8Array;
}
/**
* Compiles an array of chunks into a Buffer.
* Compiles an array of script chunks into a Uint8Array.
*
* @param chunks - The array of chunks to compile.
* @returns The compiled Buffer.
* @throws Error if the compilation fails.
* @param chunks - The chunks to compile.
* @returns The compiled script as a Uint8Array.
* @throws Error if compilation fails.
*/
function compile(chunks) {
// TODO: remove me
if (chunksIsBuffer(chunks)) return chunks;
v.parse(StackSchema, 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);
}, 0);
const buffer = new Uint8Array(bufferSize);
let offset = 0;
chunks.forEach(chunk => {
// data chunk
if (singleChunkIsBuffer(chunk)) {
// adhere to BIP62.3, minimal push policy
const opcode = asMinimalOP(chunk);
Expand All @@ -154,15 +203,19 @@ function compile(chunks) {
if (offset !== buffer.length) throw new Error('Could not decode chunks');
return buffer;
}
/**
* Decompiles a script buffer into an array of chunks.
*
* @param buffer - The script buffer to decompile.
* @returns The decompiled chunks or null if decompilation fails.
*/
function decompile(buffer) {
// TODO: remove me
if (chunksIsArray(buffer)) return buffer;
v.parse(types.BufferSchema, buffer);
const chunks = [];
let i = 0;
while (i < buffer.length) {
const opcode = buffer[i];
// data chunk
if (opcode > ops_js_1.OPS.OP_0 && opcode <= ops_js_1.OPS.OP_PUSHDATA4) {
const d = pushdata.decode(buffer, i);
// did reading a pushDataInt fail?
Expand All @@ -179,7 +232,6 @@ function decompile(buffer) {
} else {
chunks.push(data);
}
// opcode
} else {
chunks.push(opcode);
i += 1;
Expand All @@ -202,7 +254,6 @@ function toASM(chunks) {
}
return chunks
.map(chunk => {
// data?
if (singleChunkIsBuffer(chunk)) {
const op = asMinimalOP(chunk);
if (op === undefined) return tools.toHex(chunk);
Expand Down Expand Up @@ -245,13 +296,36 @@ function toStack(chunks) {
return scriptNumber.encode(op - OP_INT_BASE);
});
}
/**
* Checks if the provided buffer is a canonical public key.
*
* @param buffer - The buffer to check, expected to be a Uint8Array.
* @returns A boolean indicating whether the buffer is a canonical public key.
*/
function isCanonicalPubKey(buffer) {
return types.isPoint(buffer);
}
/**
* Checks if the provided hash type is defined.
*
* A hash type is considered defined if its modified value (after masking with ~0x80)
* is greater than 0x00 and less than 0x04.
*
* @param hashType - The hash type to check.
* @returns True if the hash type is defined, false otherwise.
*/
function isDefinedHashType(hashType) {
const hashTypeMod = hashType & ~0x80;
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
}
/**
* Checks if the provided buffer is a canonical script signature.
*
* A canonical script signature is a valid DER-encoded signature followed by a valid hash type byte.
*
* @param buffer - The buffer to check.
* @returns `true` if the buffer is a canonical script signature, `false` otherwise.
*/
function isCanonicalScriptSignature(buffer) {
if (!(buffer instanceof Uint8Array)) return false;
if (!isDefinedHashType(buffer[buffer.length - 1])) return false;
Expand Down
56 changes: 52 additions & 4 deletions src/cjs/script.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
/**
* Script tools module for working with Bitcoin scripts.
* Provides utilities such as decompiling, compiling, converting to/from ASM, stack manipulation,
* and script validation functions.
*
* @packageDocumentation
*/
import { OPS } from './ops.js';
import { Stack } from './payments/index.js';
import * as scriptNumber from './script_number.js';
import * as scriptSignature from './script_signature.js';
export { OPS };
/**
* Determines if a stack consists of only push operations.
*
* @param value - The stack to check.
* @returns True if all elements in the stack are push-only, false otherwise.
*/
export declare function isPushOnly(value: Stack): boolean;
/**
* Counts the number of non-push-only opcodes in a stack.
*
* @param value - The stack to analyze.
* @returns The count of non-push-only opcodes.
*/
export declare function countNonPushOnlyOPs(value: Stack): number;
/**
* Compiles an array of chunks into a Buffer.
* Compiles an array of script chunks into a Uint8Array.
*
* @param chunks - The array of chunks to compile.
* @returns The compiled Buffer.
* @throws Error if the compilation fails.
* @param chunks - The chunks to compile.
* @returns The compiled script as a Uint8Array.
* @throws Error if compilation fails.
*/
export declare function compile(chunks: Uint8Array | Stack): Uint8Array;
/**
* Decompiles a script buffer into an array of chunks.
*
* @param buffer - The script buffer to decompile.
* @returns The decompiled chunks or null if decompilation fails.
*/
export declare function decompile(buffer: Uint8Array | Array<number | Uint8Array>): Array<number | Uint8Array> | null;
/**
* Converts the given chunks into an ASM (Assembly) string representation.
Expand All @@ -34,8 +59,31 @@ export declare function fromASM(asm: string): Uint8Array;
* @returns The stack of buffers.
*/
export declare function toStack(chunks: Uint8Array | Array<number | Uint8Array>): Uint8Array[];
/**
* Checks if the provided buffer is a canonical public key.
*
* @param buffer - The buffer to check, expected to be a Uint8Array.
* @returns A boolean indicating whether the buffer is a canonical public key.
*/
export declare function isCanonicalPubKey(buffer: Uint8Array): boolean;
/**
* Checks if the provided hash type is defined.
*
* A hash type is considered defined if its modified value (after masking with ~0x80)
* is greater than 0x00 and less than 0x04.
*
* @param hashType - The hash type to check.
* @returns True if the hash type is defined, false otherwise.
*/
export declare function isDefinedHashType(hashType: number): boolean;
/**
* Checks if the provided buffer is a canonical script signature.
*
* A canonical script signature is a valid DER-encoded signature followed by a valid hash type byte.
*
* @param buffer - The buffer to check.
* @returns `true` if the buffer is a canonical script signature, `false` otherwise.
*/
export declare function isCanonicalScriptSignature(buffer: Uint8Array): boolean;
export declare const number: typeof scriptNumber;
export declare const signature: typeof scriptSignature;
Loading

0 comments on commit 3d53450

Please sign in to comment.