Skip to content
Merged
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
124 changes: 124 additions & 0 deletions eslint-rules/no-uint8array-tostring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
import type * as ts from 'typescript';

function hasOwnToString(type: ts.Type): boolean {
const symbol = type.getSymbol();
if (!symbol) return false;

const declarations = symbol.getDeclarations();
if (!declarations) return false;

for (const decl of declarations) {
const sourceFile = decl.getSourceFile();
// Skip Uint8Array's own toString — we only care about user-defined overrides
if (sourceFile.fileName.includes('lib.es') || sourceFile.fileName.includes('lib.dom')) {
continue;
}

if ('members' in symbol && symbol.members) {
if (symbol.members.has('toString' as ts.__String)) {
return true;
}
}
}

return false;
}

function isUint8ArrayType(type: ts.Type, checker: ts.TypeChecker): boolean {
const symbol = type.getSymbol();
if (symbol?.getName() === 'Uint8Array') {
return true;
}

const baseTypes = type.getBaseTypes?.();
if (baseTypes) {
for (const baseType of baseTypes) {
if (isUint8ArrayType(baseType, checker)) {
return true;
}
}
}

if (type.isIntersection()) {
for (const subType of type.types) {
if (isUint8ArrayType(subType, checker)) {
return true;
}
}
}

if (type.isUnion()) {
return type.types.length > 0 && type.types.every((subType) => isUint8ArrayType(subType, checker));
}

const constraint = type.getConstraint?.();
if (constraint && isUint8ArrayType(constraint, checker)) {
return true;
}

return false;
}

const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/btc-vision/eslint-rules#${name}`);

const rule = createRule({
name: 'no-uint8array-tostring',
meta: {
type: 'problem',
docs: {
description:
'Disallow .toString() on Uint8Array and branded types (Script, Bytes32, etc.) which produces comma-separated decimals instead of hex'
},
messages: {
noUint8ArrayToString:
'{{typeName}}.toString() returns comma-separated decimals (e.g. "0,32,70,107"), not a hex string. ' +
'Use Buffer.from(arr).toString("hex") or toHex() instead.'
},
schema: []
},
defaultOptions: [],
create(context) {
const services = ESLintUtils.getParserServices(context);
const checker = services.program.getTypeChecker();

return {
CallExpression(node: TSESTree.CallExpression): void {
if (
node.callee.type !== AST_NODE_TYPES.MemberExpression ||
node.callee.property.type !== AST_NODE_TYPES.Identifier ||
node.callee.property.name !== 'toString' ||
node.arguments.length > 0
) {
return;
}

const objectNode = node.callee.object;
const tsNode = services.esTreeNodeToTSNodeMap.get(objectNode);
const type = checker.getTypeAtLocation(tsNode);

if (isUint8ArrayType(type, checker) && !hasOwnToString(type)) {
const typeName = checker.typeToString(type);
context.report({
node,
messageId: 'noUint8ArrayToString',
data: { typeName }
});
}
}
};
}
});

const plugin = {
meta: {
name: 'eslint-plugin-no-uint8array-tostring',
version: '1.0.0'
},
rules: {
'no-uint8array-tostring': rule
}
};

export default plugin;
export { rule };
5 changes: 5 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import tseslint from 'typescript-eslint';
import eslint from '@eslint/js';
import noUint8ArrayToString from './eslint-rules/no-uint8array-tostring.ts';

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
plugins: {
'opnet': noUint8ArrayToString,
},
languageOptions: {
parserOptions: {
projectService: true,
Expand Down Expand Up @@ -35,6 +39,7 @@ export default tseslint.config(
'@typescript-eslint/no-unnecessary-type-arguments': 'off',
'no-debugger': 'off',
'@typescript-eslint/no-unnecessary-type-conversion': 'warn',
'opnet/no-uint8array-tostring': 'error',
},
},
{
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@
"@asyncapi/generator": "^3.1.2",
"@asyncapi/html-template": "^3.5.5",
"@bitauth/libauth": "^3.0.0",
"@btc-vision/bip32": "^7.0.2",
"@btc-vision/bitcoin": "^7.0.0-rc.0",
"@btc-vision/bip32": "^7.1.1",
"@btc-vision/bitcoin": "^7.0.0-rc.3",
"@btc-vision/bitcoin-rpc": "^1.0.6",
"@btc-vision/bsi-common": "^1.2.1",
"@btc-vision/ecpair": "^4.0.2",
"@btc-vision/ecpair": "^4.0.4",
"@btc-vision/hyper-express": "^6.17.4",
"@btc-vision/logger": "^1.0.8",
"@btc-vision/op-vm": "file:../op-vm",
"@btc-vision/plugin-sdk": "^1.0.0",
"@btc-vision/post-quantum": "^0.5.3",
"@btc-vision/rust-merkle-tree": "^0.0.5",
"@btc-vision/transaction": "^1.8.0-rc.1",
"@btc-vision/transaction": "^1.8.0-rc.2",
"@btc-vision/uwebsockets.js": "^20.57.0",
"@chainsafe/libp2p-noise": "^17.0.0",
"@chainsafe/libp2p-quic": "^1.1.8",
Expand Down Expand Up @@ -89,7 +89,6 @@
"lru-cache": "^11.2.6",
"mongodb": "^7.1.0",
"openapi-comment-parser": "^1.0.0",
"opnet": "^1.8.1-rc.1",
"protobufjs": "^8.0.0",
"sodium-native": "^5.0.10",
"ssh2": "^1.17.0",
Expand Down
7 changes: 4 additions & 3 deletions src/src/api/data-converter/TransactionConverterForAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DataConverter } from '@btc-vision/bsi-common';
import { toBase64, toHex } from '@btc-vision/bitcoin';
import { Binary } from 'mongodb';
import { OPNetTransactionTypes } from '../../blockchain-indexer/processor/transaction/enums/OPNetTransactionTypes.js';
import {
Expand Down Expand Up @@ -41,14 +42,14 @@ export class TransactionConverterForAPI {

const newTx: TransactionDocumentForAPI<OPNetTransactionTypes> = {
...transaction,
hash: transaction.hash.toString('hex'),
id: transaction.id.toString('hex'),
hash: toHex(transaction.hash),
id: toHex(transaction.id),
blockNumber:
'0x' + DataConverter.fromDecimal128(transaction.blockHeight || 0n).toString(16),
inputs: transaction.inputs?.map((input) => {
return {
...input,
originalTransactionId: input.originalTransactionId?.toString('hex'),
originalTransactionId: input.originalTransactionId ? toHex(input.originalTransactionId) : undefined,
scriptSignature: input.scriptSignature,
};
}),
Expand Down
9 changes: 5 additions & 4 deletions src/src/api/routes/api/v1/epochs/SubmitEpochRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
MLDSASecurityLevel,
QuantumBIP32Factory,
} from '@btc-vision/transaction';
import { equals } from '@btc-vision/bitcoin';
import { isEmptyBuffer } from '../../../../../utils/BufferUtils.js';

// Validate that all strings contain only valid hex characters
Expand Down Expand Up @@ -316,8 +317,8 @@ export class SubmitEpochRoute extends Route<

let message = 'Submission accepted';
if (bestSubmission) {
const currentBestSalt = Buffer.from(bestSubmission.salt.buffer);
const isWinning = bestSubmission && currentBestSalt.equals(validationParams.salt);
const currentBestSalt = new Uint8Array(bestSubmission.salt.buffer);
const isWinning = bestSubmission && equals(currentBestSalt, validationParams.salt);

if (isWinning) {
message = 'Current best submission';
Expand All @@ -334,7 +335,7 @@ export class SubmitEpochRoute extends Route<
};
}

private async validateSignature(data: EpochValidationParams): Promise<Buffer> {
private async validateSignature(data: EpochValidationParams): Promise<Uint8Array> {
if (!this.storage) {
throw new Error('Storage not initialized for signature validation');
}
Expand Down Expand Up @@ -382,7 +383,7 @@ export class SubmitEpochRoute extends Route<

const keyPair = QuantumBIP32Factory.fromPublicKey(
mldsaPublicKeyData.publicKey,
Buffer.alloc(32),
new Uint8Array(32),
this.network,
MLDSASecurityLevel.LEVEL2,
);
Expand Down
17 changes: 8 additions & 9 deletions src/src/api/routes/api/v1/shared/DeploymentTxEncoder.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toBase64, toHex } from '@btc-vision/bitcoin';
import { IContractAPIDocument } from '../../../../../db/documents/interfaces/IContractDocument.js';
import { ContractInformation } from '../../../../../blockchain-indexer/processor/transaction/contract/ContractInformation.js';
import { VMStorage } from '../../../../../vm/storage/VMStorage.js';
Expand Down Expand Up @@ -67,15 +68,13 @@ export class DeploymentTxEncoder {
private convertToBlockHeaderAPIDocument(data: ContractInformation): IContractAPIDocument {
return {
contractAddress: data.contractAddress,
contractPublicKey: Buffer.from(data.contractPublicKey.buffer).toString('base64'),
deployedTransactionId: Buffer.from(data.deployedTransactionId.buffer).toString('hex'),
deployedTransactionHash: Buffer.from(data.deployedTransactionHash.buffer).toString(
'hex',
),
bytecode: data.bytecode.toString('base64'),
deployerPubKey: data.deployerPubKey.toString('base64'),
contractSeed: data.contractSeed.toString('base64'),
contractSaltHash: data.contractSaltHash.toString('hex'),
contractPublicKey: toBase64(data.contractPublicKey.toBuffer()),
deployedTransactionId: toHex(data.deployedTransactionId),
deployedTransactionHash: toHex(data.deployedTransactionHash),
bytecode: toBase64(data.bytecode),
deployerPubKey: toBase64(data.deployerPubKey),
contractSeed: toBase64(data.contractSeed),
contractSaltHash: toHex(data.contractSaltHash),
wasCompressed: data.wasCompressed,
deployerAddress: data.deployerAddress.toHex(),
};
Expand Down
15 changes: 6 additions & 9 deletions src/src/api/routes/api/v1/states/Call.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AddressVerificator, BufferHelper, NetEvent } from '@btc-vision/transaction';
import { toBase64 } from '@btc-vision/bitcoin';
import { Request } from '@btc-vision/hyper-express/types/components/http/Request.js';
import { Response } from '@btc-vision/hyper-express/types/components/http/Response.js';
import { MiddlewareNext } from '@btc-vision/hyper-express/types/components/middleware/MiddlewareNext.js';
Expand Down Expand Up @@ -229,8 +230,8 @@ export class Call extends Route<Routes.CALL, JSONRpcMethods.CALL, CallResult | u
return data;
}

const result: string = data.result ? Buffer.from(data.result).toString('base64') : '';
const revert: string = data.revert ? Buffer.from(data.revert).toString('base64') : '';
const result: string = data.result ? toBase64(data.result) : '';
const revert: string = data.revert ? toBase64(data.revert) : '';

const accessList: AccessList = data.changedStorage
? this.getAccessList(data.changedStorage)
Expand Down Expand Up @@ -270,7 +271,7 @@ export class Call extends Route<Routes.CALL, JSONRpcMethods.CALL, CallResult | u
const eventResult: EventReceiptDataForAPI = {
contractAddress: contract,
type: event.type,
data: Buffer.from(event.data).toString('base64'),
data: toBase64(event.data),
};

contractEventsListResult.push(eventResult);
Expand All @@ -296,13 +297,9 @@ export class Call extends Route<Routes.CALL, JSONRpcMethods.CALL, CallResult | u
for (const [contract, pointerStorage] of changedStorage) {
const accessListItem: AccessListItem = {};
for (const [key, value] of pointerStorage) {
const keyStr: string = Buffer.from(BufferHelper.pointerToUint8Array(key)).toString(
'base64',
);
const keyStr: string = toBase64(BufferHelper.pointerToUint8Array(key));

accessListItem[keyStr] = Buffer.from(
BufferHelper.pointerToUint8Array(value),
).toString('base64');
accessListItem[keyStr] = toBase64(BufferHelper.pointerToUint8Array(value));
}

accessList[contract] = accessListItem;
Expand Down
5 changes: 3 additions & 2 deletions src/src/api/routes/api/v1/states/GetCode.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toBase64 } from '@btc-vision/bitcoin';
import { Request } from '@btc-vision/hyper-express/types/components/http/Request.js';
import { Response } from '@btc-vision/hyper-express/types/components/http/Response.js';
import { MiddlewareNext } from '@btc-vision/hyper-express/types/components/middleware/MiddlewareNext.js';
Expand Down Expand Up @@ -37,7 +38,7 @@ export class GetCode extends Route<
let result: GetCodeResult;
if (onlyBytecode) {
result = {
bytecode: contract.bytecode.toString('base64'),
bytecode: toBase64(contract.bytecode),
};
} else {
const document = contract.toDocument();
Expand All @@ -54,7 +55,7 @@ export class GetCode extends Route<
deployerPubKey: document.deployerPubKey.toString('base64'),
deployerAddress: document.deployerAddress.toString('base64'),

bytecode: contract.bytecode.toString('base64'),
bytecode: toBase64(contract.bytecode),

wasCompressed: document.wasCompressed,
} satisfies IContractAPIDocument;
Expand Down
10 changes: 4 additions & 6 deletions src/src/api/routes/api/v1/transaction/BroadcastTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { BroadcastResponse } from '../../../../../threading/interfaces/thread-me
import { BroadcastOPNetRequest } from '../../../../../threading/interfaces/thread-messages/messages/api/BroadcastTransactionOPNet.js';
import { TransactionSizeValidator } from '../../../../../poc/mempool/data-validator/TransactionSizeValidator.js';
import { Config } from '../../../../../config/Config.js';
import { Transaction } from '@btc-vision/bitcoin';
import { fromBase64, fromHex, Transaction } from '@btc-vision/bitcoin';

export class BroadcastTransaction extends Route<
Routes.BROADCAST_TRANSACTION,
Expand Down Expand Up @@ -55,11 +55,9 @@ export class BroadcastTransaction extends Route<
};
}

const parsedDataAsBuf = Buffer.from(data, 'hex');
const tx = Transaction.fromBuffer(parsedDataAsBuf);
let parsedData: Uint8Array = fromHex(data);
const tx = Transaction.fromBuffer(Uint8Array.from(parsedData));
const txHash = tx.getId();

let parsedData: Uint8Array = Uint8Array.from(parsedDataAsBuf);
const verification: BroadcastResponse | undefined = await this.verifyOPNetTransaction(
parsedData,
txHash,
Expand All @@ -74,7 +72,7 @@ export class BroadcastTransaction extends Route<
}

if (psbt && verification.modifiedTransaction) {
parsedData = Buffer.from(verification.modifiedTransaction, 'base64');
parsedData = fromBase64(verification.modifiedTransaction);
}

const isPsbt = verification.finalizedTransaction
Expand Down
5 changes: 3 additions & 2 deletions src/src/api/routes/api/v1/transaction/GetPreimage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { JSONRpcMethods } from '../../../../json-rpc/types/enums/JSONRpcMethods.
import { Request } from '@btc-vision/hyper-express/types/components/http/Request.js';
import { Response } from '@btc-vision/hyper-express/types/components/http/Response.js';
import { OPNetConsensus } from '../../../../../poc/configurations/OPNetConsensus.js';
import { toHex } from '@btc-vision/bitcoin';

export class GetPreimage extends Route<
Routes.TRANSACTION_PREIMAGE,
Expand Down Expand Up @@ -95,8 +96,8 @@ export class GetPreimage extends Route<
return await this.cachedData;
}

private uint8ArrayToHex(data: Uint8Array | Buffer, prefix: boolean = true): string {
const hex = Buffer.from(data).toString('hex');
private uint8ArrayToHex(data: Uint8Array, prefix: boolean = true): string {
const hex = toHex(data);
return prefix ? '0x' + hex : hex;
}

Expand Down
Loading
Loading