Skip to content

Commit 9f9bcbc

Browse files
committed
docs: improve code comment
1 parent ec1f51c commit 9f9bcbc

File tree

1 file changed

+89
-15
lines changed

1 file changed

+89
-15
lines changed

ts_src/script.ts

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
/**
2-
* Script tools, including decompile, compile, toASM, fromASM, toStack, isCanonicalPubKey, isCanonicalScriptSignature
2+
* Script tools module for working with Bitcoin scripts.
3+
* Provides utilities such as decompiling, compiling, converting to/from ASM, stack manipulation,
4+
* and script validation functions.
5+
*
36
* @packageDocumentation
47
*/
8+
59
import * as bip66 from './bip66.js';
610
import { OPS, REVERSE_OPS } from './ops.js';
711
import { Stack } from './payments/index.js';
@@ -12,11 +16,19 @@ import * as types from './types.js';
1216
import * as tools from 'uint8array-tools';
1317
import * as v from 'valibot';
1418

19+
/** Base opcode for OP_INT values. */
1520
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
1621
export { OPS };
1722

23+
/** Validation schema for a Bitcoin script stack. */
1824
const StackSchema = v.array(v.union([v.instance(Uint8Array), v.number()]));
1925

26+
/**
27+
* Determines if a value corresponds to an OP_INT opcode.
28+
*
29+
* @param value - The opcode to check.
30+
* @returns True if the value is an OP_INT, false otherwise.
31+
*/
2032
function isOPInt(value: number): boolean {
2133
return (
2234
v.is(v.number(), value) &&
@@ -26,55 +38,95 @@ function isOPInt(value: number): boolean {
2638
);
2739
}
2840

41+
/**
42+
* Checks if a script chunk is push-only (contains only data or OP_INT opcodes).
43+
*
44+
* @param value - The chunk to check.
45+
* @returns True if the chunk is push-only, false otherwise.
46+
*/
2947
function isPushOnlyChunk(value: number | Uint8Array): boolean {
3048
return v.is(types.BufferSchema, value) || isOPInt(value as number);
3149
}
3250

51+
/**
52+
* Determines if a stack consists of only push operations.
53+
*
54+
* @param value - The stack to check.
55+
* @returns True if all elements in the stack are push-only, false otherwise.
56+
*/
3357
export function isPushOnly(value: Stack): boolean {
3458
return v.is(
3559
v.pipe(v.any(), v.everyItem(isPushOnlyChunk as (x: any) => boolean)),
3660
value,
3761
);
3862
}
3963

64+
/**
65+
* Counts the number of non-push-only opcodes in a stack.
66+
*
67+
* @param value - The stack to analyze.
68+
* @returns The count of non-push-only opcodes.
69+
*/
4070
export function countNonPushOnlyOPs(value: Stack): number {
4171
return value.length - value.filter(isPushOnlyChunk).length;
4272
}
4373

74+
/**
75+
* Converts a minimal script buffer to its corresponding opcode, if applicable.
76+
*
77+
* @param buffer - The buffer to check.
78+
* @returns The corresponding opcode or undefined if not minimal.
79+
*/
4480
function asMinimalOP(buffer: Uint8Array): number | void {
4581
if (buffer.length === 0) return OPS.OP_0;
4682
if (buffer.length !== 1) return;
4783
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
4884
if (buffer[0] === 0x81) return OPS.OP_1NEGATE;
4985
}
5086

87+
/**
88+
* Determines if a buffer or stack is a Uint8Array.
89+
*
90+
* @param buf - The buffer or stack to check.
91+
* @returns True if the input is a Uint8Array, false otherwise.
92+
*/
5193
function chunksIsBuffer(buf: Uint8Array | Stack): buf is Uint8Array {
5294
return buf instanceof Uint8Array;
5395
}
5496

97+
/**
98+
* Determines if a buffer or stack is a valid stack.
99+
*
100+
* @param buf - The buffer or stack to check.
101+
* @returns True if the input is a stack, false otherwise.
102+
*/
55103
function chunksIsArray(buf: Uint8Array | Stack): buf is Stack {
56104
return v.is(StackSchema, buf);
57105
}
58106

107+
/**
108+
* Determines if a single chunk is a Uint8Array.
109+
*
110+
* @param buf - The chunk to check.
111+
* @returns True if the chunk is a Uint8Array, false otherwise.
112+
*/
59113
function singleChunkIsBuffer(buf: number | Uint8Array): buf is Uint8Array {
60114
return buf instanceof Uint8Array;
61115
}
62116

63117
/**
64-
* Compiles an array of chunks into a Buffer.
118+
* Compiles an array of script chunks into a Uint8Array.
65119
*
66-
* @param chunks - The array of chunks to compile.
67-
* @returns The compiled Buffer.
68-
* @throws Error if the compilation fails.
120+
* @param chunks - The chunks to compile.
121+
* @returns The compiled script as a Uint8Array.
122+
* @throws Error if compilation fails.
69123
*/
70124
export function compile(chunks: Uint8Array | Stack): Uint8Array {
71-
// TODO: remove me
72125
if (chunksIsBuffer(chunks)) return chunks;
73126

74127
v.parse(StackSchema, chunks);
75128

76129
const bufferSize = chunks.reduce((accum: number, chunk) => {
77-
// data chunk
78130
if (singleChunkIsBuffer(chunk)) {
79131
// adhere to BIP62.3, minimal push policy
80132
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
@@ -84,15 +136,13 @@ export function compile(chunks: Uint8Array | Stack): Uint8Array {
84136
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
85137
}
86138

87-
// opcode
88139
return accum + 1;
89-
}, 0.0);
140+
}, 0);
90141

91142
const buffer = new Uint8Array(bufferSize);
92143
let offset = 0;
93144

94145
chunks.forEach(chunk => {
95-
// data chunk
96146
if (singleChunkIsBuffer(chunk)) {
97147
// adhere to BIP62.3, minimal push policy
98148
const opcode = asMinimalOP(chunk);
@@ -117,10 +167,15 @@ export function compile(chunks: Uint8Array | Stack): Uint8Array {
117167
return buffer;
118168
}
119169

170+
/**
171+
* Decompiles a script buffer into an array of chunks.
172+
*
173+
* @param buffer - The script buffer to decompile.
174+
* @returns The decompiled chunks or null if decompilation fails.
175+
*/
120176
export function decompile(
121177
buffer: Uint8Array | Array<number | Uint8Array>,
122178
): Array<number | Uint8Array> | null {
123-
// TODO: remove me
124179
if (chunksIsArray(buffer)) return buffer;
125180

126181
v.parse(types.BufferSchema, buffer);
@@ -131,7 +186,6 @@ export function decompile(
131186
while (i < buffer.length) {
132187
const opcode = buffer[i];
133188

134-
// data chunk
135189
if (opcode > OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) {
136190
const d = pushdata.decode(buffer, i);
137191

@@ -152,8 +206,6 @@ export function decompile(
152206
} else {
153207
chunks.push(data);
154208
}
155-
156-
// opcode
157209
} else {
158210
chunks.push(opcode);
159211

@@ -179,7 +231,6 @@ export function toASM(chunks: Uint8Array | Array<number | Uint8Array>): string {
179231
}
180232
return (chunks as Stack)
181233
.map(chunk => {
182-
// data?
183234
if (singleChunkIsBuffer(chunk)) {
184235
const op = asMinimalOP(chunk);
185236
if (op === undefined) return tools.toHex(chunk);
@@ -232,16 +283,39 @@ export function toStack(
232283
});
233284
}
234285

286+
/**
287+
* Checks if the provided buffer is a canonical public key.
288+
*
289+
* @param buffer - The buffer to check, expected to be a Uint8Array.
290+
* @returns A boolean indicating whether the buffer is a canonical public key.
291+
*/
235292
export function isCanonicalPubKey(buffer: Uint8Array): boolean {
236293
return types.isPoint(buffer);
237294
}
238295

296+
/**
297+
* Checks if the provided hash type is defined.
298+
*
299+
* A hash type is considered defined if its modified value (after masking with ~0x80)
300+
* is greater than 0x00 and less than 0x04.
301+
*
302+
* @param hashType - The hash type to check.
303+
* @returns True if the hash type is defined, false otherwise.
304+
*/
239305
export function isDefinedHashType(hashType: number): boolean {
240306
const hashTypeMod = hashType & ~0x80;
241307

242308
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
243309
}
244310

311+
/**
312+
* Checks if the provided buffer is a canonical script signature.
313+
*
314+
* A canonical script signature is a valid DER-encoded signature followed by a valid hash type byte.
315+
*
316+
* @param buffer - The buffer to check.
317+
* @returns `true` if the buffer is a canonical script signature, `false` otherwise.
318+
*/
245319
export function isCanonicalScriptSignature(buffer: Uint8Array): boolean {
246320
if (!(buffer instanceof Uint8Array)) return false;
247321
if (!isDefinedHashType(buffer[buffer.length - 1])) return false;

0 commit comments

Comments
 (0)