Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/tidy-pandas-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bitauth/libauth': minor
---

Add BCH_2027 VM with OP_SIGHASH (CHIP-2026-06)
2 changes: 2 additions & 0 deletions src/lib/compiler/standard/p2pkh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const walletTemplateP2pkhNonHd: WalletTemplate = {
'BCH_2024_05',
'BCH_2025_05',
'BCH_2026_05',
'BCH_2027_05',
'BCH_SPEC',
],
};
Expand Down Expand Up @@ -106,6 +107,7 @@ export const walletTemplateP2pkh: WalletTemplate = {
'BCH_2024_05',
'BCH_2025_05',
'BCH_2026_05',
'BCH_2027_05',
'BCH_SPEC',
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ export const twoOfThree: WalletTemplate = {
'BCH_2024_05',
'BCH_2025_05',
'BCH_2026_05',
'BCH_2027_05',
],
};
16 changes: 16 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-consensus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ConsensusBch2026 } from '../2026/bch-2026-consensus.js';

/**
* Consensus setting overrides for the `BCH_2027_05` instruction set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const ConsensusBch2027Overrides = {};

/**
* Consensus settings for the `BCH_2027_05` instruction set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const ConsensusBch2027 = {
...ConsensusBch2026,
...ConsensusBch2027Overrides,
};
18 changes: 18 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-descriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { OpcodeDescriptionsBch2026 } from '../2026/bch-2026-descriptions.js';

/**
* Descriptions for the opcodes added to the `BCH_2027_05` instruction set
* beyond those present in `BCH_2026_05`.
*/
export enum OpcodeDescriptionsBch2027Additions {
OP_SIGHASH = 'Pop the top item from the stack as a sighash type (signing serialization type). Push the 32-byte sighash digest (a single SHA256 of the signing serialization) to the stack. The sighash preimage computation is identical to that of OP_CHECKSIG (including the same valid sighash types and scriptCode conventions).',
}

/**
* Descriptions for the `BCH_2027_05` instruction set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const OpcodeDescriptionsBch2027 = {
...OpcodeDescriptionsBch2026,
...OpcodeDescriptionsBch2027Additions,
};
14 changes: 14 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AuthenticationErrorBch2026 } from '../2026/bch-2026-errors.js';

export enum AuthenticationErrorBch2027Additions {
invalidSighashType = 'Program attempted an OP_SIGHASH operation with an invalid sighash type.',
}

/**
* Errors for the `BCH_2027_05` instruction set.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const AuthenticationErrorBch2027 = {
...AuthenticationErrorBch2026,
...AuthenticationErrorBch2027Additions,
};
90 changes: 90 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-instruction-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
ripemd160 as internalRipemd160,
secp256k1 as internalSecp256k1,
sha1 as internalSha1,
sha256 as internalSha256,
} from '../../../../crypto/crypto.js';
import type {
AuthenticationProgramBch,
AuthenticationProgramStateBch2027,
InstructionSet,
ResolvedTransactionBch,
Ripemd160,
Secp256k1,
Sha1,
Sha256,
} from '../../../../lib.js';
import { conditionallyEvaluate } from '../../common/common.js';
import { createInstructionSetBch2026 } from '../2026/bch-2026-instruction-set.js';

import { ConsensusBch2027 } from './bch-2027-consensus.js';
import { OpcodesBch2027 } from './bch-2027-opcodes.js';
import { opSighash } from './bch-2027-sighash.js';

/**
* Initialize a virtual machine using the `BCH_2027_05` instruction set.
*
* @param standard - If `true`, the additional `isStandard` validations will be
* enabled. Transactions that fail these rules are often called "non-standard"
* and can technically be included by miners in valid blocks, but most network
* nodes will refuse to relay them. (Default: `true`)
*/
export const createInstructionSetBch2027 = <
AuthenticationProgramState extends AuthenticationProgramStateBch2027,
Consensus extends typeof ConsensusBch2027 = typeof ConsensusBch2027,
>(
standard = true,
{
consensus = ConsensusBch2027 as Consensus,
ripemd160,
secp256k1,
sha1,
sha256,
}: {
consensus?: Consensus;
/**
* a Ripemd160 implementation
*/
ripemd160: { hash: Ripemd160['hash'] };
/**
* a Secp256k1 implementation
*/
secp256k1: {
verifySignatureSchnorr: Secp256k1['verifySignatureSchnorr'];
verifySignatureDERLowS: Secp256k1['verifySignatureDERLowS'];
};
/**
* a Sha1 implementation
*/
sha1: { hash: Sha1['hash'] };
/**
* a Sha256 implementation
*/
sha256: { hash: Sha256['hash'] };
} = {
ripemd160: internalRipemd160,
secp256k1: internalSecp256k1,
sha1: internalSha1,
sha256: internalSha256,
},
): InstructionSet<
ResolvedTransactionBch,
AuthenticationProgramBch,
AuthenticationProgramState
> => {
const instructionSet =
createInstructionSetBch2026<AuthenticationProgramState>(standard, {
consensus,
ripemd160,
secp256k1,
sha1,
sha256,
});
return {
...instructionSet,
operations: {
...instructionSet.operations,
[OpcodesBch2027.OP_SIGHASH]: conditionallyEvaluate(opSighash({ sha256 })),
},
};
};
28 changes: 28 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-opcodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { OpcodesBch2026 } from '../2026/bch-2026-opcodes.js';

/**
* The opcodes added to the `BCH_2027_05` instruction set beyond those present
* in `BCH_2026_05`.
*/
export enum OpcodesBch2027Additions {
/**
* Formerly `OP_UNKNOWN189`
*
* See CHIP-2026-06 OP_SIGHASH:
* https://gitlab.com/0353F40E/sighash
*/
OP_SIGHASH = 0xbd,
}

/**
* The `BCH_2027_05` instruction set.
*
* Note: to maximize script compilation compatibility, this instruction set
* also includes the previous names for new opcodes (e.g. `OP_UNKNOWN189` for
* `OP_SIGHASH`).
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const OpcodesBch2027 = {
...OpcodesBch2026,
...OpcodesBch2027Additions,
};
76 changes: 76 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-sighash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { sha256 as internalSha256 } from '../../../../crypto/crypto.js';
import { binToHex } from '../../../../format/format.js';
import type {
AuthenticationProgramStateCommon,
Sha256,
} from '../../../../lib.js';
import {
applyError,
encodeAuthenticationInstructions,
lengthToHashDigestIterationCount,
pushToStack,
useOneStackItem,
} from '../../common/common.js';
import { SigningSerializationTypesBch } from '../../common/consensus.js';
import { generateSigningSerializationBch } from '../../common/signing-serialization.js';

import { AuthenticationErrorBch2027 } from './bch-2027-errors.js';

/**
* The `OP_SIGHASH` operation (CHIP-2026-06 OP_SIGHASH).
*
* This is the first half of `OP_CHECKSIG`: pop one byte from the stack as the
* sighash type, compute the sighash preimage exactly as `OP_CHECKSIG` would,
* and push the resulting 32-byte sighash digest to the stack.
*
* The one difference from `OP_CHECKSIG` is the final hash: a single SHA256
* instead of a double SHA256 (`hash256`), so the result drops straight into
* `OP_CHECKDATASIG` (which hashes the message once before verifying).
*
* Note, unlike `OP_CHECKSIG` and `OP_CHECKDATASIG`, `OP_SIGHASH` does not
* verify a signature, so it does not increment `signatureCheckCount`. Like
* `OP_CHECKSIG` internally, the worst-case hashing cost of the preimage
* computation is always charged to `hashDigestIterations`, even when a cached
* digest can be returned.
*
* See CHIP-2026-06 OP_SIGHASH: https://gitlab.com/0353F40E/sighash
*/
export const opSighash =
<State extends AuthenticationProgramStateCommon>(
{ sha256 }: { sha256: { hash: Sha256['hash'] } } = {
sha256: internalSha256,
},
): ((state: State) => State) =>
(state: State) =>
useOneStackItem(state, (nextState, [sighashType]) => {
if (
sighashType.length !== 1 ||
!SigningSerializationTypesBch.includes(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sighashType[0]!,
)
) {
return applyError(
nextState,
AuthenticationErrorBch2027.invalidSighashType,
`Valid sighash types: [${SigningSerializationTypesBch.map(
(type) => `0x${binToHex(Uint8Array.of(type))}`,
).join(', ')}].`,
);
}
const coveredBytecode = encodeAuthenticationInstructions(
nextState.instructions.slice(nextState.lastCodeSeparator + 1),
);
const signingSerializationType = sighashType;
const serialization = generateSigningSerializationBch(
nextState.program,
{ coveredBytecode, signingSerializationType },
sha256,
);
const digest = sha256.hash(serialization);
/* eslint-disable functional/no-expression-statements, functional/immutable-data */
nextState.metrics.hashDigestIterations +=
lengthToHashDigestIterationCount(serialization.length);
/* eslint-enable functional/no-expression-statements, functional/immutable-data */
return pushToStack(nextState, [digest], { pushedBytes: 32 });
});
15 changes: 15 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type {
AuthenticationProgramBch,
AuthenticationProgramStateBch2026,
AuthenticationVirtualMachine,
ResolvedTransactionBch,
} from '../../../../lib.js';

export type AuthenticationProgramStateBch2027 =
AuthenticationProgramStateBch2026;

export type AuthenticationVirtualMachineBch2027 = AuthenticationVirtualMachine<
ResolvedTransactionBch,
AuthenticationProgramBch,
AuthenticationProgramStateBch2027
>;
14 changes: 14 additions & 0 deletions src/lib/vm/instruction-sets/bch/2027/bch-2027-vm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createVirtualMachine } from '../../../virtual-machine.js';

import { createInstructionSetBch2027 } from './bch-2027-instruction-set.js';

/**
* Initialize a virtual machine using the `BCH_2027_05` instruction set.
*
* @param standard - If `true`, the additional `isStandard` validations will be
* enabled. Transactions that fail these rules are often called "non-standard"
* and can technically be included by miners in valid blocks, but most network
* nodes will refuse to relay them. (Default: `true`)
*/
export const createVirtualMachineBch2027 = (standard = true) =>
createVirtualMachine(createInstructionSetBch2027(standard));
Loading
Loading