Skip to content

Commit 19d8f72

Browse files
authored
Merge pull request #2190 from ethsmartcoder/master
Improve Documentation for Script Tools Module
2 parents ec1f51c + 3d53450 commit 19d8f72

File tree

4 files changed

+320
-50
lines changed

4 files changed

+320
-50
lines changed

src/cjs/script.cjs

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
'use strict';
2+
/**
3+
* Script tools module for working with Bitcoin scripts.
4+
* Provides utilities such as decompiling, compiling, converting to/from ASM, stack manipulation,
5+
* and script validation functions.
6+
*
7+
* @packageDocumentation
8+
*/
29
var __createBinding =
310
(this && this.__createBinding) ||
411
(Object.create
@@ -55,10 +62,6 @@ exports.toStack = toStack;
5562
exports.isCanonicalPubKey = isCanonicalPubKey;
5663
exports.isDefinedHashType = isDefinedHashType;
5764
exports.isCanonicalScriptSignature = isCanonicalScriptSignature;
58-
/**
59-
* Script tools, including decompile, compile, toASM, fromASM, toStack, isCanonicalPubKey, isCanonicalScriptSignature
60-
* @packageDocumentation
61-
*/
6265
const bip66 = __importStar(require('./bip66.cjs'));
6366
const ops_js_1 = require('./ops.cjs');
6467
Object.defineProperty(exports, 'OPS', {
@@ -73,8 +76,16 @@ const scriptSignature = __importStar(require('./script_signature.cjs'));
7376
const types = __importStar(require('./types.cjs'));
7477
const tools = __importStar(require('uint8array-tools'));
7578
const v = __importStar(require('valibot'));
79+
/** Base opcode for OP_INT values. */
7680
const OP_INT_BASE = ops_js_1.OPS.OP_RESERVED; // OP_1 - 1
81+
/** Validation schema for a Bitcoin script stack. */
7782
const StackSchema = v.array(v.union([v.instance(Uint8Array), v.number()]));
83+
/**
84+
* Determines if a value corresponds to an OP_INT opcode.
85+
*
86+
* @param value - The opcode to check.
87+
* @returns True if the value is an OP_INT, false otherwise.
88+
*/
7889
function isOPInt(value) {
7990
return (
8091
v.is(v.number(), value) &&
@@ -83,57 +94,95 @@ function isOPInt(value) {
8394
value === ops_js_1.OPS.OP_1NEGATE)
8495
);
8596
}
97+
/**
98+
* Checks if a script chunk is push-only (contains only data or OP_INT opcodes).
99+
*
100+
* @param value - The chunk to check.
101+
* @returns True if the chunk is push-only, false otherwise.
102+
*/
86103
function isPushOnlyChunk(value) {
87104
return v.is(types.BufferSchema, value) || isOPInt(value);
88105
}
106+
/**
107+
* Determines if a stack consists of only push operations.
108+
*
109+
* @param value - The stack to check.
110+
* @returns True if all elements in the stack are push-only, false otherwise.
111+
*/
89112
function isPushOnly(value) {
90113
return v.is(v.pipe(v.any(), v.everyItem(isPushOnlyChunk)), value);
91114
}
115+
/**
116+
* Counts the number of non-push-only opcodes in a stack.
117+
*
118+
* @param value - The stack to analyze.
119+
* @returns The count of non-push-only opcodes.
120+
*/
92121
function countNonPushOnlyOPs(value) {
93122
return value.length - value.filter(isPushOnlyChunk).length;
94123
}
124+
/**
125+
* Converts a minimal script buffer to its corresponding opcode, if applicable.
126+
*
127+
* @param buffer - The buffer to check.
128+
* @returns The corresponding opcode or undefined if not minimal.
129+
*/
95130
function asMinimalOP(buffer) {
96131
if (buffer.length === 0) return ops_js_1.OPS.OP_0;
97132
if (buffer.length !== 1) return;
98133
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0];
99134
if (buffer[0] === 0x81) return ops_js_1.OPS.OP_1NEGATE;
100135
}
136+
/**
137+
* Determines if a buffer or stack is a Uint8Array.
138+
*
139+
* @param buf - The buffer or stack to check.
140+
* @returns True if the input is a Uint8Array, false otherwise.
141+
*/
101142
function chunksIsBuffer(buf) {
102143
return buf instanceof Uint8Array;
103144
}
145+
/**
146+
* Determines if a buffer or stack is a valid stack.
147+
*
148+
* @param buf - The buffer or stack to check.
149+
* @returns True if the input is a stack, false otherwise.
150+
*/
104151
function chunksIsArray(buf) {
105152
return v.is(StackSchema, buf);
106153
}
154+
/**
155+
* Determines if a single chunk is a Uint8Array.
156+
*
157+
* @param buf - The chunk to check.
158+
* @returns True if the chunk is a Uint8Array, false otherwise.
159+
*/
107160
function singleChunkIsBuffer(buf) {
108161
return buf instanceof Uint8Array;
109162
}
110163
/**
111-
* Compiles an array of chunks into a Buffer.
164+
* Compiles an array of script chunks into a Uint8Array.
112165
*
113-
* @param chunks - The array of chunks to compile.
114-
* @returns The compiled Buffer.
115-
* @throws Error if the compilation fails.
166+
* @param chunks - The chunks to compile.
167+
* @returns The compiled script as a Uint8Array.
168+
* @throws Error if compilation fails.
116169
*/
117170
function compile(chunks) {
118-
// TODO: remove me
119171
if (chunksIsBuffer(chunks)) return chunks;
120172
v.parse(StackSchema, chunks);
121173
const bufferSize = chunks.reduce((accum, chunk) => {
122-
// data chunk
123174
if (singleChunkIsBuffer(chunk)) {
124175
// adhere to BIP62.3, minimal push policy
125176
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
126177
return accum + 1;
127178
}
128179
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
129180
}
130-
// opcode
131181
return accum + 1;
132-
}, 0.0);
182+
}, 0);
133183
const buffer = new Uint8Array(bufferSize);
134184
let offset = 0;
135185
chunks.forEach(chunk => {
136-
// data chunk
137186
if (singleChunkIsBuffer(chunk)) {
138187
// adhere to BIP62.3, minimal push policy
139188
const opcode = asMinimalOP(chunk);
@@ -154,15 +203,19 @@ function compile(chunks) {
154203
if (offset !== buffer.length) throw new Error('Could not decode chunks');
155204
return buffer;
156205
}
206+
/**
207+
* Decompiles a script buffer into an array of chunks.
208+
*
209+
* @param buffer - The script buffer to decompile.
210+
* @returns The decompiled chunks or null if decompilation fails.
211+
*/
157212
function decompile(buffer) {
158-
// TODO: remove me
159213
if (chunksIsArray(buffer)) return buffer;
160214
v.parse(types.BufferSchema, buffer);
161215
const chunks = [];
162216
let i = 0;
163217
while (i < buffer.length) {
164218
const opcode = buffer[i];
165-
// data chunk
166219
if (opcode > ops_js_1.OPS.OP_0 && opcode <= ops_js_1.OPS.OP_PUSHDATA4) {
167220
const d = pushdata.decode(buffer, i);
168221
// did reading a pushDataInt fail?
@@ -179,7 +232,6 @@ function decompile(buffer) {
179232
} else {
180233
chunks.push(data);
181234
}
182-
// opcode
183235
} else {
184236
chunks.push(opcode);
185237
i += 1;
@@ -202,7 +254,6 @@ function toASM(chunks) {
202254
}
203255
return chunks
204256
.map(chunk => {
205-
// data?
206257
if (singleChunkIsBuffer(chunk)) {
207258
const op = asMinimalOP(chunk);
208259
if (op === undefined) return tools.toHex(chunk);
@@ -245,13 +296,36 @@ function toStack(chunks) {
245296
return scriptNumber.encode(op - OP_INT_BASE);
246297
});
247298
}
299+
/**
300+
* Checks if the provided buffer is a canonical public key.
301+
*
302+
* @param buffer - The buffer to check, expected to be a Uint8Array.
303+
* @returns A boolean indicating whether the buffer is a canonical public key.
304+
*/
248305
function isCanonicalPubKey(buffer) {
249306
return types.isPoint(buffer);
250307
}
308+
/**
309+
* Checks if the provided hash type is defined.
310+
*
311+
* A hash type is considered defined if its modified value (after masking with ~0x80)
312+
* is greater than 0x00 and less than 0x04.
313+
*
314+
* @param hashType - The hash type to check.
315+
* @returns True if the hash type is defined, false otherwise.
316+
*/
251317
function isDefinedHashType(hashType) {
252318
const hashTypeMod = hashType & ~0x80;
253319
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
254320
}
321+
/**
322+
* Checks if the provided buffer is a canonical script signature.
323+
*
324+
* A canonical script signature is a valid DER-encoded signature followed by a valid hash type byte.
325+
*
326+
* @param buffer - The buffer to check.
327+
* @returns `true` if the buffer is a canonical script signature, `false` otherwise.
328+
*/
255329
function isCanonicalScriptSignature(buffer) {
256330
if (!(buffer instanceof Uint8Array)) return false;
257331
if (!isDefinedHashType(buffer[buffer.length - 1])) return false;

src/cjs/script.d.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,43 @@
1+
/**
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+
*
6+
* @packageDocumentation
7+
*/
18
import { OPS } from './ops.js';
29
import { Stack } from './payments/index.js';
310
import * as scriptNumber from './script_number.js';
411
import * as scriptSignature from './script_signature.js';
512
export { OPS };
13+
/**
14+
* Determines if a stack consists of only push operations.
15+
*
16+
* @param value - The stack to check.
17+
* @returns True if all elements in the stack are push-only, false otherwise.
18+
*/
619
export declare function isPushOnly(value: Stack): boolean;
20+
/**
21+
* Counts the number of non-push-only opcodes in a stack.
22+
*
23+
* @param value - The stack to analyze.
24+
* @returns The count of non-push-only opcodes.
25+
*/
726
export declare function countNonPushOnlyOPs(value: Stack): number;
827
/**
9-
* Compiles an array of chunks into a Buffer.
28+
* Compiles an array of script chunks into a Uint8Array.
1029
*
11-
* @param chunks - The array of chunks to compile.
12-
* @returns The compiled Buffer.
13-
* @throws Error if the compilation fails.
30+
* @param chunks - The chunks to compile.
31+
* @returns The compiled script as a Uint8Array.
32+
* @throws Error if compilation fails.
1433
*/
1534
export declare function compile(chunks: Uint8Array | Stack): Uint8Array;
35+
/**
36+
* Decompiles a script buffer into an array of chunks.
37+
*
38+
* @param buffer - The script buffer to decompile.
39+
* @returns The decompiled chunks or null if decompilation fails.
40+
*/
1641
export declare function decompile(buffer: Uint8Array | Array<number | Uint8Array>): Array<number | Uint8Array> | null;
1742
/**
1843
* Converts the given chunks into an ASM (Assembly) string representation.
@@ -34,8 +59,31 @@ export declare function fromASM(asm: string): Uint8Array;
3459
* @returns The stack of buffers.
3560
*/
3661
export declare function toStack(chunks: Uint8Array | Array<number | Uint8Array>): Uint8Array[];
62+
/**
63+
* Checks if the provided buffer is a canonical public key.
64+
*
65+
* @param buffer - The buffer to check, expected to be a Uint8Array.
66+
* @returns A boolean indicating whether the buffer is a canonical public key.
67+
*/
3768
export declare function isCanonicalPubKey(buffer: Uint8Array): boolean;
69+
/**
70+
* Checks if the provided hash type is defined.
71+
*
72+
* A hash type is considered defined if its modified value (after masking with ~0x80)
73+
* is greater than 0x00 and less than 0x04.
74+
*
75+
* @param hashType - The hash type to check.
76+
* @returns True if the hash type is defined, false otherwise.
77+
*/
3878
export declare function isDefinedHashType(hashType: number): boolean;
79+
/**
80+
* Checks if the provided buffer is a canonical script signature.
81+
*
82+
* A canonical script signature is a valid DER-encoded signature followed by a valid hash type byte.
83+
*
84+
* @param buffer - The buffer to check.
85+
* @returns `true` if the buffer is a canonical script signature, `false` otherwise.
86+
*/
3987
export declare function isCanonicalScriptSignature(buffer: Uint8Array): boolean;
4088
export declare const number: typeof scriptNumber;
4189
export declare const signature: typeof scriptSignature;

0 commit comments

Comments
 (0)