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+
59import * as bip66 from './bip66.js' ;
610import { OPS , REVERSE_OPS } from './ops.js' ;
711import { Stack } from './payments/index.js' ;
@@ -12,11 +16,19 @@ import * as types from './types.js';
1216import * as tools from 'uint8array-tools' ;
1317import * as v from 'valibot' ;
1418
19+ /** Base opcode for OP_INT values. */
1520const OP_INT_BASE = OPS . OP_RESERVED ; // OP_1 - 1
1621export { OPS } ;
1722
23+ /** Validation schema for a Bitcoin script stack. */
1824const 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+ */
2032function 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+ */
2947function 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+ */
3357export 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+ */
4070export 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+ */
4480function 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+ */
5193function 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+ */
55103function 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+ */
59113function 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 */
70124export 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+ */
120176export 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+ */
235292export 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+ */
239305export 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+ */
245319export function isCanonicalScriptSignature ( buffer : Uint8Array ) : boolean {
246320 if ( ! ( buffer instanceof Uint8Array ) ) return false ;
247321 if ( ! isDefinedHashType ( buffer [ buffer . length - 1 ] ) ) return false ;
0 commit comments