Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for 2025 Cash Improvement Proposals and VM benchmarking #139

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b4fbd00
clarify generateDeterministicEntropy usage examples
bitjson Apr 10, 2024
4043ae7
Deprecate BCH_2022_05, add BCH_2023_05, BCH_2025_05, and BCH_SPEC, up…
bitjson May 8, 2024
49bcce4
Begin implementation of BCH_2025_05 and BCH_SPEC
bitjson May 10, 2024
1f34ca4
Update `isStandardOutputBytecode` to allow P2SH32
bitjson May 28, 2024
18e1614
Add BCH_2026_05, add VM benchmarking
bitjson May 30, 2024
c0eadac
Run VMB benchmarks in CI
bitjson May 30, 2024
d0d54b9
Fix OP_TXVERSION VMB tests, reduce 2025 nonstandard hashing density l…
bitjson May 31, 2024
1a03d3f
Improve VM perf, support schnorr multisig, expand support for limit, …
bitjson Jul 26, 2024
a867ff9
Merge branch 'benchmarks' into next
bitjson Jul 26, 2024
ffad3f6
Enter prerelease mode
bitjson Jul 26, 2024
b1851d4
Release v3.1.0-next.0 (#138)
github-actions[bot] Jul 26, 2024
cad78dd
Revise BCH VMs and VMB tests, rewrite VMB test tooling for performance
bitjson Sep 17, 2024
9c35e3e
Upgrade deps, add export:vmb_tests tooling, more tests
bitjson Sep 25, 2024
546ae14
Merge pull request #144 from bitauth/2025
bitjson Sep 27, 2024
33bbdaa
Release v (next)
github-actions[bot] Sep 27, 2024
94306e4
Merge pull request #146 from bitauth/changeset-release/next
bitjson Sep 27, 2024
76879be
Submodule and test against BCHN, expand VMB test coverage
bitjson Nov 15, 2024
1ae893c
Merge pull request #148 from bitauth/2025
bitjson Nov 15, 2024
10b1eeb
Release v (next)
github-actions[bot] Nov 15, 2024
681f962
Merge pull request #149 from bitauth/changeset-release/next
bitjson Nov 15, 2024
836d917
Speculatively implement P2S, OP_EVAL, and OP_POW (#150)
bitjson Dec 12, 2024
febc970
Release v3.1.0-next.3 (#151)
github-actions[bot] Dec 12, 2024
3075c46
Fix build issues
bitjson Dec 13, 2024
4efa3fb
Commit BCH_2026 tests
bitjson Dec 13, 2024
477334a
Improve BCH_2026 support and test coverage
bitjson Dec 19, 2024
489cac0
Release v3.1.0-next.4 (#152)
github-actions[bot] Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Speculatively implement P2S, OP_EVAL, and OP_POW (#150)
  • Loading branch information
bitjson authored Dec 12, 2024
commit 836d9172f556889150ced64d8d991d1dfa3d3c79
5 changes: 5 additions & 0 deletions .changeset/tall-actors-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bitauth/libauth': minor
---

Speculatively implement P2S, OP_EVAL, and OP_POW
2 changes: 2 additions & 0 deletions src/lib/engine/types/template-types.ts
Original file line number Diff line number Diff line change
@@ -841,6 +841,8 @@ export type WalletTemplateScriptLocking = WalletTemplateScript & {
* The presence of the `lockingType` property indicates that this script is a
* locking script. It must be present on any script referenced by the
* `unlocks` property of another script.
*
* TODO: migrate `standard` -> `p2s`
*/
lockingType: 'p2sh20' | 'p2sh32' | 'standard';
};
19 changes: 13 additions & 6 deletions src/lib/language/language-utils.ts
Original file line number Diff line number Diff line change
@@ -651,7 +651,8 @@ export const extractEvaluationSamples = <
* outer evaluation appear before their parent sample (which uses their result).
*/
export const extractEvaluationSamplesRecursive = <
ProgramState extends AuthenticationProgramStateMinimum,
ProgramState extends AuthenticationProgramStateControlStack &
AuthenticationProgramStateMinimum,
>({
/**
* The range of the script node that was evaluated to produce the `trace`
@@ -670,6 +671,8 @@ export const extractEvaluationSamplesRecursive = <
nodes: ScriptReductionTraceScriptNode<ProgramState>['script'];
trace: ProgramState[];
}): SampleExtractionResult<ProgramState> => {
const statesNotProducedByOpEval = (state: ProgramState) =>
!state.controlStack.some((item) => typeof item === 'object');
const extractEvaluations = (
node: ScriptReductionTraceChildNode<ProgramState>,
depth = 1,
@@ -690,7 +693,9 @@ export const extractEvaluationSamplesRecursive = <
],
[],
);
const traceWithoutUnlockingPhase = node.trace.slice(1);
const traceWithoutUnlockingPhase = node.trace
.slice(1)
.filter(statesNotProducedByOpEval);
const evaluationBeginToken = '$(';
const evaluationEndToken = ')';
const extracted = extractEvaluationSamples<ProgramState>({
@@ -711,7 +716,7 @@ export const extractEvaluationSamplesRecursive = <
const { samples, unmatchedStates } = extractEvaluationSamples<ProgramState>({
evaluationRange,
nodes,
trace,
trace: trace.filter(statesNotProducedByOpEval),
});

const childSamples = nodes.reduce<EvaluationSample<ProgramState>[]>(
@@ -825,6 +830,7 @@ export const extractUnexecutedRanges = <
return containedRangesExcluded;
};

const oneBelowHash160 = 19;
/**
* Given a stack, return a summary of the stack's contents, encoding valid VM
* numbers as numbers, and all other stack items as hex literals.
@@ -833,7 +839,9 @@ export const extractUnexecutedRanges = <
*/
export const summarizeStack = (stack: Uint8Array[]) =>
stack.map((item) => {
const asNumber = vmNumberToBigInt(item);
const asNumber = vmNumberToBigInt(item, {
maximumVmNumberByteLength: oneBelowHash160,
});
return `0x${binToHex(item)}${
typeof asNumber === 'string' ? '' : `(${asNumber.toString()})`
}`;
@@ -879,8 +887,7 @@ export const summarizeDebugTrace = <
...(nextState.error === undefined
? {}
: { error: nextState.error }),
execute:
state.controlStack[state.controlStack.length - 1] !== false,
execute: state.controlStack.every((item) => item !== false),
instruction:
'instruction' in state
? state.instruction
6 changes: 5 additions & 1 deletion src/lib/vm/instruction-sets/bch/2023/bch-2023-consensus.ts
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@ export const ConsensusBch2023 = {
* A.K.A. `MAX_SCRIPT_SIZE`
*/
maximumBytecodeLength: 10000,
maximumCommitmentLength: 40,
/**
* A.K.A. `MAX_CONSENSUS_VERSION`
*/
@@ -40,6 +39,10 @@ export const ConsensusBch2023 = {
* A.K.A. `MAX_SCRIPT_ELEMENT_SIZE`
*/
maximumStackItemLength: 520,
/**
* When set to `-1`, only BCH_2023_05 standard patterns are accepted.
*/
maximumStandardLockingBytecodeLength: -1,
/**
* A.K.A. `MAX_STANDARD_TX_SIZE`
*/
@@ -48,6 +51,7 @@ export const ConsensusBch2023 = {
* A.K.A. `MAX_TX_IN_SCRIPT_SIG_SIZE`
*/
maximumStandardUnlockingBytecodeLength: 1650,
maximumTokenCommitmentLength: 40,
/**
* A.K.A. `MAX_TX_SIZE`
*/
50 changes: 37 additions & 13 deletions src/lib/vm/instruction-sets/bch/2023/bch-2023-instruction-set.ts
Original file line number Diff line number Diff line change
@@ -911,10 +911,20 @@ export const createInstructionSetBch2023 = <

// eslint-disable-next-line functional/no-loop-statements
for (const [index, output] of sourceOutputs.entries()) {
if (!isStandardUtxoBytecode(output.lockingBytecode)) {
if (consensus.maximumStandardLockingBytecodeLength === -1) {
if (!isStandardUtxoBytecode(output.lockingBytecode)) {
return formatError(
AuthenticationErrorCommon.verifyStandardFailedNonstandardSourceOutput,
`Source output ${index} is non-standard: locking bytecode does not match a standard pattern: P2PKH, P2PK, P2SH, P2MS, or arbitrary data (OP_RETURN).`,
);
}
} else if (
output.lockingBytecode.length >
consensus.maximumStandardLockingBytecodeLength
) {
return formatError(
AuthenticationErrorCommon.verifyStandardFailedNonstandardSourceOutput,
`Source output ${index} is non-standard: locking bytecode does not match a standard pattern: P2PKH, P2PK, P2SH, P2MS, or arbitrary data (OP_RETURN).`,
`Source output ${index} is non-standard: locking bytecode length of ${output.lockingBytecode.length} exceeds the maximum standard locking bytecode length of ${consensus.maximumStandardLockingBytecodeLength}.`,
);
}
}
@@ -923,21 +933,32 @@ export const createInstructionSetBch2023 = <
let totalArbitraryDataBytes = 0;
// eslint-disable-next-line functional/no-loop-statements
for (const [index, output] of transaction.outputs.entries()) {
if (!isStandardOutputBytecode(output.lockingBytecode)) {
return formatError(
AuthenticationErrorCommon.verifyStandardFailedNonstandardOutput,
`Transaction output ${index} is non-standard: locking bytecode does not match a standard pattern: P2PKH, P2PK, P2SH, P2MS, or arbitrary data (OP_RETURN).`,
);
}
// eslint-disable-next-line functional/no-conditional-statements
if (isArbitraryDataOutput(output.lockingBytecode)) {
// eslint-disable-next-line functional/no-expression-statements
totalArbitraryDataBytes += output.lockingBytecode.length + 1;
if (consensus.maximumStandardLockingBytecodeLength === -1) {
if (!isStandardOutputBytecode(output.lockingBytecode)) {
return formatError(
AuthenticationErrorCommon.verifyStandardFailedNonstandardOutput,
`Transaction output ${index} is non-standard: locking bytecode does not match a standard pattern: P2PKH, P2PK, P2SH, P2MS, or arbitrary data (OP_RETURN).`,
);
}
} else if (
output.lockingBytecode.length >
consensus.maximumStandardLockingBytecodeLength
) {
// eslint-disable-next-line functional/no-conditional-statements
if (isArbitraryDataOutput(output.lockingBytecode)) {
// eslint-disable-next-line functional/no-expression-statements
totalArbitraryDataBytes += output.lockingBytecode.length + 1;
} else {
return formatError(
AuthenticationErrorCommon.verifyStandardFailedNonstandardOutput,
`Transaction output ${index} is non-standard: locking bytecode length of ${output.lockingBytecode.length} exceeds the maximum standard locking bytecode length of ${consensus.maximumStandardLockingBytecodeLength} and does not match the standard arbitrary data pattern (OP_RETURN).`,
);
}
}
if (isDustOutput(output)) {
return formatError(
AuthenticationErrorCommon.verifyStandardFailedDustOutput,
` Transaction output ${index} must have a value of at least ${getDustThreshold(
`Transaction output ${index} must have a value of at least ${getDustThreshold(
output,
)} satoshis. Current value: ${output.valueSatoshis}`,
);
@@ -973,6 +994,9 @@ export const createInstructionSetBch2023 = <
const tokenValidationResult = verifyTransactionTokens(
transaction,
sourceOutputs,
{
maximumTokenCommitmentLength: consensus.maximumTokenCommitmentLength,
},
);
if (tokenValidationResult !== true) {
return tokenValidationResult;
8 changes: 3 additions & 5 deletions src/lib/vm/instruction-sets/bch/2023/bch-2023-tokens.ts
Original file line number Diff line number Diff line change
@@ -177,19 +177,17 @@ export const extractTransactionOutputTokenData = (
export const verifyTransactionTokens = (
transaction: Transaction,
sourceOutputs: Output[],
{ maximumTokenCommitmentLength }: { maximumTokenCommitmentLength: number },
) => {
const excessiveCommitment = [...sourceOutputs, ...transaction.outputs].find(
(output) =>
output.token?.nft?.commitment !== undefined &&
output.token.nft.commitment.length >
ConsensusBch2023.maximumCommitmentLength,
output.token.nft.commitment.length > maximumTokenCommitmentLength,
);
if (excessiveCommitment !== undefined) {
return formatError(
AuthenticationErrorCommon.tokenValidationExcessiveCommitmentLength,
`A token commitment exceeds the consensus limit of ${
ConsensusBch2023.maximumCommitmentLength
} bytes. Excessive token commitment length: ${
`A token commitment exceeds the consensus limit of ${maximumTokenCommitmentLength} bytes. Excessive token commitment length: ${
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
excessiveCommitment.token!.nft!.commitment.length
}`,
5 changes: 4 additions & 1 deletion src/lib/vm/instruction-sets/bch/2026/bch-2026-consensus.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,10 @@ import { ConsensusBch2025 } from '../2025/bch-2025-consensus.js';
* Consensus setting overrides for the `BCH_SPEC` instruction set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const ConsensusBch2026Overrides = {};
export const ConsensusBch2026Overrides = {
maximumStandardLockingBytecodeLength: 201,
maximumTokenCommitmentLength: 80,
};

/**
* Consensus settings for the `BCH_SPEC` instruction set.
20 changes: 20 additions & 0 deletions src/lib/vm/instruction-sets/bch/2026/bch-2026-descriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { OpcodeDescriptionsBch2023 } from '../2023/bch-2023-descriptions.js';

/**
* Descriptions for the opcodes added to the `BCH_2026_05` instruction set
* beyond those present in `BCH_2025_05`.
*/
export enum OpcodeDescriptionsBch2026Additions {
OP_EVAL = 'Pop the top item from the stack as bytecode. Preserve the active bytecode at the top of the control stack, then evaluate the bytecode as if it were the active bytecode (without modifying the stack, alternate stack, or other evaluation context). When the evaluation is complete, restore the original bytecode and continue evaluation after the OP_EVAL instruction. If the bytecode is malformed, error.',
OP_BEGIN = 'Push the current instruction pointer index to the control stack as an integer (to be read by OP_UNTIL).',
OP_UNTIL = 'Pop the top item from the control stack (if the control value is not an integer, error). Add the difference between the control value and the current instruction pointer index to the repeated bytes counter, if the sum of the repeated bytes counter and the active bytecode length is greater than the maximum bytecode length, error. Pop the top item from the stack, if the value is not truthy, move the instruction pointer to the control value (and re-evaluate the OP_BEGIN).',
}

/**
* Descriptions for the `BCH_SPEC` instruction set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const OpcodeDescriptionsBch2026 = {
...OpcodeDescriptionsBch2023,
...OpcodeDescriptionsBch2026Additions,
};
1 change: 1 addition & 0 deletions src/lib/vm/instruction-sets/bch/2026/bch-2026-errors.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ export enum AuthenticationErrorBch2026Additions {
unexpectedUntil = 'Encountered an OP_UNTIL that is not following a matching OP_BEGIN.',
unexpectedUntilMissingEndIf = 'Encountered an OP_UNTIL before the previous OP_IF was closed by an OP_ENDIF.',
excessiveLooping = 'Program attempted an OP_UNTIL operation that would exceed the limit of repeated bytes.',
malformedEval = 'Program attempted to OP_EVAL malformed bytecode.',
}

/**
49 changes: 49 additions & 0 deletions src/lib/vm/instruction-sets/bch/2026/bch-2026-eval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type {
AuthenticationInstructionMalformed,
AuthenticationProgramStateBch2026,
} from '../../../../lib.js';
import {
applyError,
authenticationInstructionsAreMalformed,
decodeAuthenticationInstructions,
disassembleAuthenticationInstructionMalformed,
executionIsActive,
pushToControlStack,
useOneStackItem,
} from '../../common/common.js';

import { AuthenticationErrorBch2026 } from './bch-2026-errors.js';
import { OpcodesBch2026 } from './bch-2026-opcodes.js';

export const opEval = <State extends AuthenticationProgramStateBch2026>(
state: State,
) => {
if (executionIsActive(state)) {
return useOneStackItem(state, (nextState, [item]) => {
const newInstructions = decodeAuthenticationInstructions(item);

if (authenticationInstructionsAreMalformed(newInstructions)) {
return applyError(
nextState,
AuthenticationErrorBch2026.malformedEval,
`Malformed instruction: ${disassembleAuthenticationInstructionMalformed(
OpcodesBch2026,
newInstructions[
newInstructions.length - 1
] as AuthenticationInstructionMalformed,
)}.`,
);
}

const manuallyAdvance = 1;
const finalState = pushToControlStack(nextState, {
instructions: nextState.instructions,
ip: nextState.ip + manuallyAdvance,
});
finalState.ip = 0 - manuallyAdvance; // eslint-disable-line functional/no-expression-statements, functional/immutable-data
finalState.instructions = newInstructions; // eslint-disable-line functional/no-expression-statements, functional/immutable-data
return finalState;
});
}
return state;
};
19 changes: 19 additions & 0 deletions src/lib/vm/instruction-sets/bch/2026/bch-2026-instruction-set.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
} from '../../../../crypto/crypto.js';
import type {
AuthenticationProgramBch,
AuthenticationProgramStackFrame,
InstructionSet,
ResolvedTransactionBch,
Ripemd160,
@@ -17,6 +18,7 @@ import { createInstructionSetBch2025 } from '../2025/bch-2025-instruction-set.js
import { opBegin, opUntil } from '../2026/bch-2026-loops.js';

import { ConsensusBch2026 } from './bch-2026-consensus.js';
import { opEval } from './bch-2026-eval.js';
import { OpcodesBch2026 } from './bch-2026-opcodes.js';
import type { AuthenticationProgramStateBch2026 } from './bch-2026-types.js';
/**
@@ -80,13 +82,30 @@ export const createInstructionSetBch2026 = <
});
return {
...instructionSet,
/* eslint-disable functional/no-loop-statements, functional/immutable-data, functional/no-expression-statements */
continue: (state) => {
if (state.error !== undefined) return false;
while (
state.ip >= state.instructions.length &&
state.controlStack.length > 0 &&
typeof state.controlStack[state.controlStack.length - 1] === 'object'
) {
const { instructions, ip } =
state.controlStack.pop() as AuthenticationProgramStackFrame;
state.ip = ip;
state.instructions = instructions;
}
return state.ip < state.instructions.length;
},
/* eslint-enable functional/no-loop-statements, functional/immutable-data, functional/no-expression-statements */
initialize: (program) =>
({
...instructionSet.initialize?.(program),
repeatedBytes: 0,
}) as Partial<AuthenticationProgramStateBch2026> as Partial<AuthenticationProgramState>,
operations: {
...instructionSet.operations,
[OpcodesBch2026.OP_EVAL]: opEval,
[OpcodesBch2026.OP_BEGIN]: opBegin,
[OpcodesBch2026.OP_UNTIL]: opUntil,
},
9 changes: 5 additions & 4 deletions src/lib/vm/instruction-sets/bch/2026/bch-2026-loops.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@ import {
stackItemIsTruthy,
useOneStackItem,
} from '../../common/common.js';
import { AuthenticationErrorBchSpec } from '../spec/bch-spec-errors.js';

import { AuthenticationErrorBch2026 } from './bch-2026-errors.js';

const enum Constants {
markInactiveOpBegin = -1,
@@ -27,14 +28,14 @@ export const opUntil = <State extends AuthenticationProgramStateBch2026>(
// eslint-disable-next-line functional/immutable-data
const controlValue = state.controlStack.pop();
if (typeof controlValue !== 'number') {
return applyError(state, AuthenticationErrorBchSpec.unexpectedUntil);
return applyError(state, AuthenticationErrorBch2026.unexpectedUntil);
}
if (!executionIsActive(state)) {
return controlValue === Constants.markInactiveOpBegin
? state
: applyError(
state,
AuthenticationErrorBchSpec.unexpectedUntilMissingEndIf,
AuthenticationErrorBch2026.unexpectedUntilMissingEndIf,
);
}

@@ -51,7 +52,7 @@ export const opUntil = <State extends AuthenticationProgramStateBch2026>(
) {
return applyError(
state,
AuthenticationErrorBchSpec.excessiveLooping,
AuthenticationErrorBch2026.excessiveLooping,
`Repeated bytes: ${state.repeatedBytes}; active bytecode length: ${activeBytecodeLength}`,
);
}
1 change: 1 addition & 0 deletions src/lib/vm/instruction-sets/bch/2026/bch-2026-opcodes.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { OpcodesBch2023 } from '../2023/bch-2023-opcodes.js';
* `BCH_2023_05`.
*/
export enum OpcodesBch2026Additions {
OP_EVAL = 0x62,
OP_BEGIN = 0x65,
OP_UNTIL = 0x66,
}
Loading
Loading