Skip to content

Commit a4da66c

Browse files
committed
Add CashAssembly features, improve VM performance
1 parent 28b9aeb commit a4da66c

File tree

205 files changed

+10152
-45961
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

205 files changed

+10152
-45961
lines changed

.changeset/bright-steaks-explode.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bitauth/libauth': minor
3+
---
4+
5+
Add CashAssembly support for stack item labels

.changeset/cold-icons-kneel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bitauth/libauth': minor
3+
---
4+
5+
add debug and compileCashAssembly utilities

.changeset/dull-fireants-float.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bitauth/libauth': patch
3+
---
4+
5+
Improve VM Number encoding/decoding performance

.changeset/silver-bottles-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bitauth/libauth': patch
3+
---
4+
5+
Update 2026 CHIP implementations

src/lib/compiler/compiler-utils.ts

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {
2525
WalletTemplate,
2626
} from '../lib.js';
2727
import {
28-
encodeDataPush,
28+
createVirtualMachineBchSpec,
2929
generateBytecodeMap,
3030
Opcodes,
3131
OpcodesBchSpec,
@@ -125,7 +125,6 @@ export const compilerConfigurationToCompiler =
125125
compilerConfigurationToCompilerBch;
126126

127127
const nullHashLength = 32;
128-
const maximumValidOpReturnPushLength = 9996;
129128

130129
/**
131130
* A common {@link createAuthenticationProgram} implementation for
@@ -165,10 +164,7 @@ export const createAuthenticationProgramEvaluationCommon = (
165164
locktime: 0,
166165
outputs: [
167166
{
168-
lockingBytecode: flattenBinArray([
169-
Uint8Array.of(Opcodes.OP_RETURN),
170-
encodeDataPush(new Uint8Array(maximumValidOpReturnPushLength)),
171-
]),
167+
lockingBytecode: flattenBinArray([Uint8Array.of(Opcodes.OP_RETURN)]),
172168
valueSatoshis: 0n,
173169
},
174170
],
@@ -233,6 +229,122 @@ export const compileCashAssembly = (script: string) => {
233229
)}`;
234230
};
235231

232+
const defaultVm = createVirtualMachineBchSpec();
233+
234+
/**
235+
* Compile a CashAssembly script with detailed debugging information.
236+
*
237+
* If no VM override is provided, Libauth's default `BCH_SPEC` VM is used.
238+
*
239+
* @param script - the CashAssembly script to compile
240+
* @param scriptsAndOverrides - a compiler configuration from which properties
241+
* will be used to override properties of the default common compiler
242+
* configuration
243+
*/
244+
export const debugCashAssemblyCompilation = <
245+
Configuration extends CompilerConfiguration<CompilationContextBch>,
246+
Overrides extends Configuration & {
247+
vm?: NonNullable<Configuration['vm']>;
248+
},
249+
>(
250+
script: string,
251+
scriptsAndOverrides: Overrides = { scripts: {}, vm: defaultVm } as Overrides,
252+
) => {
253+
const merged = {
254+
...scriptsAndOverrides,
255+
scripts: {
256+
script,
257+
...scriptsAndOverrides.scripts,
258+
},
259+
...(scriptsAndOverrides.vm ? scriptsAndOverrides.vm : { vm: defaultVm }),
260+
};
261+
return createCompilerCommon(merged).generateScenario({
262+
debug: true,
263+
lockingScriptId: 'script',
264+
});
265+
};
266+
267+
/**
268+
* A simple, compile-and-evaluate utility for debugging CashAssembly scripts.
269+
* Returns the full compilation results, program trace, and verification result;
270+
* to return only the final program state following evaluation,
271+
* use {@link evaluateCashAssembly}.
272+
*
273+
* If no VM override is provided, Libauth's default `BCH_SPEC` VM is used.
274+
*
275+
* @param script - the CashAssembly script to debug
276+
* @param scriptsAndOverrides - a compiler configuration from which properties
277+
* will be used to override properties of the default common compiler
278+
* configuration
279+
*/
280+
export const debugCashAssembly = <
281+
Configuration extends CompilerConfiguration<CompilationContextBch>,
282+
ProgramState extends AuthenticationProgramStateCommon,
283+
Overrides extends Configuration & {
284+
vm?: NonNullable<Configuration['vm']>;
285+
},
286+
>(
287+
script: string,
288+
scriptsAndOverrides: Overrides = { scripts: {}, vm: defaultVm } as Overrides,
289+
) => {
290+
const vm = scriptsAndOverrides.vm ?? defaultVm;
291+
const result = debugCashAssemblyCompilation<Configuration, Overrides>(
292+
script,
293+
{ ...scriptsAndOverrides, vm },
294+
);
295+
if (typeof result === 'string')
296+
return {
297+
error: result,
298+
success: false,
299+
} as { error: string; success: false };
300+
if (typeof result.scenario === 'string')
301+
return {
302+
error: result.scenario,
303+
result,
304+
success: false as const,
305+
} as { error: string; result: typeof result; success: false };
306+
const trace = vm.debug(result.scenario.program) as ProgramState[];
307+
const verify = vm.verify(result.scenario.program);
308+
return { compilation: result, success: true, trace, verify } as {
309+
compilation: typeof result;
310+
success: true;
311+
trace: typeof trace;
312+
verify: typeof verify;
313+
};
314+
};
315+
316+
/**
317+
* A simple, compile-and-evaluate utility for testing CashAssembly scripts.
318+
* Returns the final program state following evaluation; for full compilation
319+
* debugging and a program trace, use {@link debugCashAssembly}.
320+
*
321+
* If no VM override is provided, Libauth's default `BCH_SPEC` VM is used.
322+
*
323+
* @param script - the CashAssembly script to evaluate
324+
* @param scriptsAndOverrides - a compiler configuration from which properties
325+
* will be used to override properties of the default common compiler
326+
* configuration
327+
*/
328+
export const evaluateCashAssembly = <
329+
Configuration extends CompilerConfiguration<CompilationContextBch>,
330+
ProgramState extends AuthenticationProgramStateCommon,
331+
Overrides extends Configuration & {
332+
vm?: NonNullable<Configuration['vm']>;
333+
},
334+
>(
335+
script: string,
336+
scriptsAndOverrides: Overrides = { scripts: {}, vm: defaultVm } as Overrides,
337+
) => {
338+
const vm = scriptsAndOverrides.vm ?? defaultVm;
339+
const debug = debugCashAssembly<Configuration, ProgramState, Overrides>(
340+
script,
341+
{ ...scriptsAndOverrides, vm },
342+
);
343+
if (!debug.success) return debug.error;
344+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
345+
return debug.trace[debug.trace.length - 1]!;
346+
};
347+
236348
/**
237349
* Re-assemble a string of disassembled bytecode
238350
* (see {@link disassembleBytecode}).

src/lib/language/compile.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ test('compileScript: empty string', (t) => {
111111
script: [
112112
{
113113
bytecode: Uint8Array.of(),
114+
comment: '',
114115
range: {
115116
endColumn: 1,
116117
endLineNumber: 1,
@@ -170,6 +171,7 @@ test('compileScriptContents: empty string', (t) => {
170171
script: [
171172
{
172173
bytecode: Uint8Array.of(),
174+
comment: '',
173175
range: {
174176
endColumn: 1,
175177
endLineNumber: 1,
@@ -224,6 +226,7 @@ test('compileScriptContents: empty script (script with space)', (t) => {
224226
script: [
225227
{
226228
bytecode: Uint8Array.of(),
229+
comment: '',
227230
range: {
228231
endColumn: 5,
229232
endLineNumber: 1,
@@ -1265,6 +1268,7 @@ test('compileScript: comments', (t) => {
12651268
script: [
12661269
{
12671270
bytecode: Uint8Array.of(),
1271+
comment: 'single-line',
12681272
range: {
12691273
endColumn: 15,
12701274
endLineNumber: 1,
@@ -1274,6 +1278,7 @@ test('compileScript: comments', (t) => {
12741278
},
12751279
{
12761280
bytecode: Uint8Array.of(),
1281+
comment: 'multi-\nline',
12771282
range: {
12781283
endColumn: 8,
12791284
endLineNumber: 3,

src/lib/language/language-types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,15 @@ export type ScriptReductionTraceNode = {
255255
errors?: CompilationError[] | undefined;
256256
range: Range;
257257
};
258-
type ScriptReductionTraceErrorNode = ScriptReductionTraceNode & {
258+
259+
export type ScriptReductionTraceErrorNode = ScriptReductionTraceNode & {
259260
errors: CompilationError[];
260261
};
261262

263+
export type ScriptReductionTraceCommentNode = ScriptReductionTraceNode & {
264+
comment: string;
265+
};
266+
262267
export type ScriptReductionTraceScriptNode<ProgramState> =
263268
ScriptReductionTraceNode & {
264269
script: ScriptReductionTraceChildNode<ProgramState>[];
@@ -276,6 +281,7 @@ export type ScriptReductionTraceEvaluationNode<ProgramState> =
276281
};
277282

278283
export type ScriptReductionTraceChildNode<ProgramState> =
284+
| ScriptReductionTraceCommentNode
279285
| ScriptReductionTraceErrorNode
280286
| ScriptReductionTraceEvaluationNode<ProgramState>
281287
| ScriptReductionTraceNode

src/lib/language/language-utils.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
createCompilerCommon,
1313
createVirtualMachineBch,
1414
createVirtualMachineBchSpec,
15+
evaluateCashAssembly,
1516
extractBytecodeResolutions,
1617
extractEvaluationSamples,
1718
extractEvaluationSamplesRecursive,
@@ -83,6 +84,21 @@ test('containsRange', (t) => {
8384
);
8485
});
8586

87+
test('evaluateCashAssembly', (t) => {
88+
const successful = assertSuccess(evaluateCashAssembly('<2> <2> OP_ADD'));
89+
t.deepEqual(
90+
successful.stack[0],
91+
hexToBin('04'),
92+
stringifyTestVector(successful),
93+
);
94+
const failed = evaluateCashAssembly('<bad>');
95+
t.deepEqual(
96+
failed,
97+
'Cannot generate the default scenario: Failed compilation of source output at index 0: Unknown identifier "bad".',
98+
stringifyTestVector(failed),
99+
);
100+
});
101+
86102
test('compileCashAssembly', (t) => {
87103
const successful = compileCashAssembly('<0x010203>');
88104
t.deepEqual(
@@ -190,6 +206,7 @@ test('extractEvaluationSamples: empty trace', (t) => {
190206
t.deepEqual(
191207
result,
192208
{
209+
labeledStackItems: [],
193210
samples: [],
194211
unmatchedStates: [],
195212
},
@@ -259,6 +276,17 @@ OP_BEGIN
259276
OP_EQUAL
260277
OP_UNTIL
261278
OP_NIP`,
279+
loopsInternalConditional: `
280+
<5>
281+
OP_BEGIN
282+
OP_1SUB
283+
OP_SIZE
284+
OP_IF
285+
<0>
286+
OP_ELSE
287+
<1>
288+
OP_ENDIF
289+
OP_UNTIL`,
262290
nested: `OP_0
263291
264292
<
@@ -7654,6 +7682,13 @@ test(extractUnexecutedRangesMacro, 'unexecuted11', [
76547682

76557683
test(extractUnexecutedRangesMacro, 'unexecutedEmpty', []);
76567684

7685+
test.todo(
7686+
'extractUnexecutedRangesMacro: loopsInternalConditional at loop 0 iteration 0',
7687+
);
7688+
test.todo(
7689+
'extractUnexecutedRangesMacro: loopsInternalConditional at loop 0 iteration 4',
7690+
);
7691+
76577692
test('summarizeDebugTrace, stringifyDebugTraceSummary', (t) => {
76587693
const { program } = assertSuccess(
76597694
walletTemplateToCompilerBch({
@@ -7765,3 +7800,8 @@ test('summarizeDebugTrace, stringifyDebugTraceSummary (error)', (t) => {
77657800
stringifyTestVector(formatted),
77667801
);
77677802
});
7803+
7804+
test.todo('extraction of labeledStackItems');
7805+
test.todo(
7806+
'consider adding support for recursive extraction of labeledStackItems',
7807+
);

0 commit comments

Comments
 (0)