Skip to content

Commit dcd7185

Browse files
committed
Implement Bitwise CHIP in BCH_2026
1 parent 0c1f0b8 commit dcd7185

34 files changed

+1414
-37
lines changed

.changeset/fifty-bears-cross.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 support for Bitwise CHIP to BCH_2026

.cspell.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@
103103
"lifecycle",
104104
"locktime",
105105
"LSHIFT",
106+
"LSHIFTBIN",
107+
"LSHIFTNUM",
106108
"mainnet",
107109
"malleate",
108110
"malleated",
@@ -156,6 +158,8 @@
156158
"ripemd",
157159
"rkalis",
158160
"RSHIFT",
161+
"RSHIFTBIN",
162+
"RSHIFTNUM",
159163
"rustup",
160164
"satoshi",
161165
"satoshis",
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import type {
2+
AuthenticationProgramStateError,
3+
AuthenticationProgramStateResourceLimits,
4+
AuthenticationProgramStateStack,
5+
} from '../../../../lib.js';
6+
import {
7+
applyError,
8+
pushToStack,
9+
pushToStackVmNumberChecked,
10+
useOneStackItem,
11+
useOneVmNumber,
12+
} from '../../common/common.js';
13+
14+
import { ConsensusBch2026 } from './bch-2026-consensus.js';
15+
import { AuthenticationErrorBch2026 } from './bch-2026-errors.js';
16+
17+
const enum Constants {
18+
allBits = 0xff,
19+
bitsPerByte = 8,
20+
}
21+
22+
export const opInvert = <
23+
State extends AuthenticationProgramStateError &
24+
AuthenticationProgramStateStack,
25+
>(
26+
state: State,
27+
): State =>
28+
useOneStackItem(state, (nextState, [a]) =>
29+
// eslint-disable-next-line no-bitwise
30+
pushToStack(nextState, [a.map((v) => v ^ Constants.allBits)]),
31+
);
32+
33+
const useOneShiftBitCount = <
34+
State extends AuthenticationProgramStateError &
35+
AuthenticationProgramStateResourceLimits &
36+
AuthenticationProgramStateStack,
37+
>(
38+
state: State,
39+
shiftOperation: (nextState: State, [bitCount]: [bigint]) => State,
40+
{ maximumStackItemLength = ConsensusBch2026.maximumStackItemLength } = {},
41+
) => {
42+
const maximumBitCount = maximumStackItemLength * Constants.bitsPerByte;
43+
44+
return useOneVmNumber(state, (nextState, [bitCount]) => {
45+
if (bitCount < 0n || bitCount > maximumBitCount) {
46+
return applyError(
47+
nextState,
48+
AuthenticationErrorBch2026.invalidShiftBitCount,
49+
`Bit count (${bitCount}) is outside of the valid range: 0 to ${maximumBitCount} (inclusive).`,
50+
);
51+
}
52+
return shiftOperation(nextState, [bitCount]);
53+
});
54+
};
55+
56+
const createOpShiftNum =
57+
(
58+
maximumStackItemLength: number,
59+
shiftOperation: (numericValue: bigint, bitCount: bigint) => bigint,
60+
) =>
61+
<
62+
State extends AuthenticationProgramStateError &
63+
AuthenticationProgramStateResourceLimits &
64+
AuthenticationProgramStateStack,
65+
>(
66+
state: State,
67+
): State =>
68+
useOneShiftBitCount(
69+
state,
70+
(nextState, [bitCount]) =>
71+
useOneVmNumber(nextState, (finalState, [numericValue]) => {
72+
const result = shiftOperation(numericValue, bitCount);
73+
return pushToStackVmNumberChecked(finalState, result, {
74+
hasEncodingCost: true,
75+
maximumVmNumberByteLength: maximumStackItemLength,
76+
});
77+
}),
78+
{ maximumStackItemLength },
79+
);
80+
81+
export const createOpLShiftNum = ({
82+
maximumStackItemLength = ConsensusBch2026.maximumStackItemLength,
83+
} = {}) =>
84+
createOpShiftNum(
85+
maximumStackItemLength,
86+
// eslint-disable-next-line no-bitwise
87+
(numericValue, bitCount) => numericValue << bitCount,
88+
);
89+
90+
export const createOpRShiftNum = ({
91+
maximumStackItemLength = ConsensusBch2026.maximumStackItemLength,
92+
} = {}) =>
93+
createOpShiftNum(
94+
maximumStackItemLength,
95+
// eslint-disable-next-line no-bitwise
96+
(numericValue, bitCount) => numericValue >> bitCount,
97+
);
98+
99+
// eslint-disable-next-line functional/no-return-void
100+
const copyWholeBytes = (
101+
src: Uint8Array,
102+
dst: Uint8Array,
103+
byteShift: number,
104+
left: boolean,
105+
// eslint-disable-next-line @typescript-eslint/max-params
106+
) => {
107+
if (byteShift >= src.length) return;
108+
if (left) {
109+
// eslint-disable-next-line functional/no-expression-statements
110+
dst.set(src.subarray(byteShift), 0);
111+
return;
112+
}
113+
// eslint-disable-next-line functional/no-expression-statements
114+
dst.set(src.subarray(0, src.length - byteShift), byteShift);
115+
};
116+
117+
const residualLeftShift = (buf: Uint8Array, bitShift: number): Uint8Array => {
118+
// eslint-disable-next-line functional/no-let
119+
let carry = 0;
120+
// eslint-disable-next-line functional/no-loop-statements, functional/no-let, no-plusplus
121+
for (let i = buf.length - 1; i >= 0; i--) {
122+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123+
const v = buf[i]!;
124+
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, no-bitwise
125+
buf[i] = ((v << bitShift) | carry) & Constants.allBits;
126+
// eslint-disable-next-line functional/no-expression-statements, no-bitwise
127+
carry = v >> (Constants.bitsPerByte - bitShift);
128+
}
129+
return buf;
130+
};
131+
132+
const residualRightShift = (buf: Uint8Array, bitShift: number): Uint8Array => {
133+
// eslint-disable-next-line functional/no-let
134+
let carry = 0;
135+
// eslint-disable-next-line functional/no-loop-statements, functional/no-let, no-plusplus
136+
for (let i = 0; i < buf.length; i++) {
137+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
138+
const v = buf[i]!;
139+
// eslint-disable-next-line functional/no-expression-statements, functional/immutable-data, no-bitwise
140+
buf[i] = ((v >> bitShift) | carry) & Constants.allBits;
141+
// eslint-disable-next-line functional/no-expression-statements, no-bitwise
142+
carry = (v << (Constants.bitsPerByte - bitShift)) & Constants.allBits;
143+
}
144+
return buf;
145+
};
146+
147+
const shiftFixed = (
148+
src: Uint8Array,
149+
bitCount: bigint,
150+
isLeftShift: boolean,
151+
): Uint8Array => {
152+
const s = Number(bitCount);
153+
if (!s || !src.length) return src.slice();
154+
155+
const len = src.length;
156+
const dst = new Uint8Array(len);
157+
const byteShift = Math.floor(s / Constants.bitsPerByte);
158+
const bitShift = s % Constants.bitsPerByte;
159+
160+
// eslint-disable-next-line functional/no-expression-statements
161+
copyWholeBytes(src, dst, byteShift, isLeftShift);
162+
if (!bitShift) return dst;
163+
164+
return isLeftShift
165+
? residualLeftShift(dst, bitShift)
166+
: residualRightShift(dst, bitShift);
167+
};
168+
169+
/**
170+
* Perform a fixed-length, logical left shift of `bin` by the `bitCount`,
171+
* equivalent to `OP_LSHIFTBIN` in the Bitcoin Cash VM.
172+
* @param bin - the Uint8Array to shift.
173+
* @param bitCount - the count of bits by which to shift `bin`.
174+
*/
175+
export const binaryShiftLeft = (bin: Uint8Array, bitCount: bigint) =>
176+
shiftFixed(bin, bitCount, true);
177+
178+
/**
179+
* Perform a fixed-length, logical right shift of `bin` by the `bitCount`,
180+
* equivalent to `OP_RSHIFTBIN` in the Bitcoin Cash VM.
181+
* @param bin - the Uint8Array to shift.
182+
* @param bitCount - the count of bits by which to shift `bin`.
183+
*/
184+
export const binaryShiftRight = (bin: Uint8Array, bitCount: bigint) =>
185+
shiftFixed(bin, bitCount, false);
186+
187+
const createOpShiftBin =
188+
(
189+
maximumStackItemLength: number,
190+
shiftOperation: (bin: Uint8Array, bitCount: bigint) => Uint8Array,
191+
) =>
192+
<
193+
State extends AuthenticationProgramStateError &
194+
AuthenticationProgramStateResourceLimits &
195+
AuthenticationProgramStateStack,
196+
>(
197+
state: State,
198+
): State =>
199+
useOneShiftBitCount(
200+
state,
201+
(nextState, [bitCount]) =>
202+
useOneStackItem(nextState, (finalState, [bin]) =>
203+
pushToStack(finalState, [shiftOperation(bin, bitCount)]),
204+
),
205+
{ maximumStackItemLength },
206+
);
207+
208+
export const createOpLShiftBin = ({
209+
maximumStackItemLength = ConsensusBch2026.maximumStackItemLength,
210+
} = {}) => createOpShiftBin(maximumStackItemLength, binaryShiftLeft);
211+
212+
export const createOpRShiftBin = ({
213+
maximumStackItemLength = ConsensusBch2026.maximumStackItemLength,
214+
} = {}) => createOpShiftBin(maximumStackItemLength, binaryShiftRight);

src/lib/vm/instruction-sets/bch/2026/bch-2026-descriptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ import { OpcodeDescriptionsBch2023 } from '../2023/bch-2023-descriptions.js';
77
export enum OpcodeDescriptionsBch2026Additions {
88
OP_BEGIN = 'Push the current instruction pointer index to the control stack as an integer (to be read by OP_UNTIL).',
99
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).',
10+
OP_INVERT = `Pop the top item from the stack. Invert (bitwise "NOT") each byte of the item, then push the result to the stack.`,
1011
OP_DEFINE = 'Pop the top item from the stack to interpret as a function identifier (VM number between 0 and 999, inclusive). Pop the next item from the stack (the function body), and save it to the function table at the index equal to the function identifier. If that function index is out of range or already defined, error.',
1112
OP_INVOKE = 'Pop the top item from the stack to interpret as a function table index (VM number between 0 and 999, inclusive). If no function is defined at that index, error. Preserve the active bytecode at the top of the control stack, then evaluate the function body at that function table index as if it were the active bytecode (without resetting the stack, alternate stack, or evaluation limits). When the evaluation is complete, restore the original bytecode and continue evaluation after the OP_INVOKE instruction. If the bytecode is malformed, error.',
13+
OP_LSHIFTNUM = 'Pop the top item from the stack as a bit count (VM number). Pop the next item from the stack as the value (VM number) to shift. Perform an arithmetic left shift of the value by the bit count (`result = value * (2 ^ bit_count)`), then push the result to the stack.',
14+
OP_RSHIFTNUM = 'Pop the top item from the stack as a bit count (VM number). Pop the next item from the stack as the value (VM number) to shift. Perform an arithmetic right shift of the value by the bit count (`result = value / (2 ^ bit_count)`) rounding towards negative infinity, then push the resulting VM number to the stack.',
15+
OP_LSHIFTBIN = 'Pop the top item from the stack as a bit count (VM number). Pop the next item from the stack as the binary data to shift. Perform a fixed-length, logical left shift of the data by the bit count, shifting-in `0` bits from the right and dropping shifted-out bits on the left, then push the result to the stack.',
16+
OP_RSHIFTBIN = 'Pop the top item from the stack as a bit count (VM number). Pop the next item from the stack as the binary data to shift. Perform a fixed-length, logical right shift of the data by the bit count, shifting-in `0` bits from the left and dropping shifted-out bits on the right, then push the result to the stack.',
1217
}
1318

1419
/**

src/lib/vm/instruction-sets/bch/2026/bch-2026-errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export enum AuthenticationErrorBch2026Additions {
88
functionIdentifierPreviouslyDefined = 'Program attempted to OP_DEFINE a previously-defined function identifier.',
99
functionIdentifierUndefined = 'Program attempted to OP_INVOKE an undefined function identifier.',
1010
malformedFunction = 'Program attempted to OP_INVOKE malformed bytecode.',
11+
invalidShiftBitCount = 'Program attempted a bitwise shift with an invalid bit count.',
1112
}
1213

1314
/**

src/lib/vm/instruction-sets/bch/2026/bch-2026-instruction-set.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import { conditionallyEvaluate } from '../../common/common.js';
1818
import { createInstructionSetBch2025 } from '../2025/bch-2025-instruction-set.js';
1919
import { opBegin, opUntil } from '../2026/bch-2026-loops.js';
2020

21+
import {
22+
createOpLShiftBin,
23+
createOpLShiftNum,
24+
createOpRShiftBin,
25+
createOpRShiftNum,
26+
opInvert,
27+
} from './bch-2026-bitwise.js';
2128
import { ConsensusBch2026 } from './bch-2026-consensus.js';
2229
import { createOpDefine, opInvoke } from './bch-2026-functions.js';
2330
import { OpcodesBch2026 } from './bch-2026-opcodes.js';
@@ -103,10 +110,23 @@ export const createInstructionSetBch2026 = <
103110
...instructionSet.operations,
104111
[OpcodesBch2026.OP_BEGIN]: opBegin,
105112
[OpcodesBch2026.OP_UNTIL]: opUntil,
113+
[OpcodesBch2026.OP_INVERT]: conditionallyEvaluate(opInvert),
106114
[OpcodesBch2026.OP_DEFINE]: conditionallyEvaluate(
107115
createOpDefine(consensus),
108116
),
109117
[OpcodesBch2026.OP_INVOKE]: conditionallyEvaluate(opInvoke),
118+
[OpcodesBch2026.OP_LSHIFTNUM]: conditionallyEvaluate(
119+
createOpLShiftNum(consensus),
120+
),
121+
[OpcodesBch2026.OP_RSHIFTNUM]: conditionallyEvaluate(
122+
createOpRShiftNum(consensus),
123+
),
124+
[OpcodesBch2026.OP_LSHIFTBIN]: conditionallyEvaluate(
125+
createOpLShiftBin(consensus),
126+
),
127+
[OpcodesBch2026.OP_RSHIFTBIN]: conditionallyEvaluate(
128+
createOpRShiftBin(consensus),
129+
),
110130
},
111131
};
112132
};

src/lib/vm/instruction-sets/bch/2026/bch-2026-opcodes.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ export enum OpcodesBch2026Additions {
2525
/**
2626
* Formerly `OP_2MUL`
2727
*/
28-
OP_MULSHIFT = 0x8d,
28+
OP_LSHIFTNUM = 0x8d,
2929
/**
3030
* Formerly `OP_2DIV`
3131
*/
32-
OP_DIVSHIFT = 0x8e,
32+
OP_RSHIFTNUM = 0x8e,
3333
/**
3434
*Formerly `OP_LSHIFT`
3535
*/
36-
OP_PADRIGHT = 0x98,
36+
OP_LSHIFTBIN = 0x98,
3737
/**
3838
*Formerly `OP_RSHIFT`
3939
*/
40-
OP_PADLEFT = 0x99,
40+
OP_RSHIFTBIN = 0x99,
4141
}
4242

4343
/**

src/lib/vm/instruction-sets/common/combinators.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ export const useOneVmNumber = <
199199
return operation(nextState, [value]);
200200
});
201201

202+
/**
203+
* Note that returned parameters are in source order,
204+
* e.g. `<first> <second> OP_CODE`.
205+
*/
202206
export const useTwoVmNumbers = <
203207
State extends AuthenticationProgramStateError &
204208
AuthenticationProgramStateStack,
@@ -237,6 +241,10 @@ export const useTwoVmNumbers = <
237241
},
238242
);
239243

244+
/**
245+
* Note that returned parameters are in source order,
246+
* e.g. `<first> <second> <third> OP_CODE`.
247+
*/
240248
export const useThreeVmNumbers = <
241249
State extends AuthenticationProgramStateError &
242250
AuthenticationProgramStateStack,
@@ -352,7 +360,7 @@ export const pushToStackVmNumberChecked = <
352360
vmNumber: bigint,
353361
{
354362
maximumVmNumberByteLength = ConsensusCommon.maximumVmNumberByteLength as number,
355-
hasEncodingCost = false,
363+
hasEncodingCost = true,
356364
} = {},
357365
) => {
358366
const encoded = bigIntToVmNumber(vmNumber);

0 commit comments

Comments
 (0)