Skip to content

Commit

Permalink
add tests for Uint8Array in jsdom
Browse files Browse the repository at this point in the history
  • Loading branch information
Muhammad-Altabba committed Jan 3, 2024
1 parent 992d5bf commit 62c8c85
Show file tree
Hide file tree
Showing 5 changed files with 885 additions and 7 deletions.
248 changes: 248 additions & 0 deletions packages/web3-eth-accounts/test/unit/account_dom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/**
* @jest-environment jsdom
*/

/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

// this file contains the unit test for the event emitter in the DOM environment
// it is executed in the jsdom environment (see "@jest-environment jsdom" in the top comment of this file)

// ignore the following rule to allow keeping `@jest-environment jsdom` on top:
// eslint-disable-next-line header/header
import { TextEncoder } from 'util';
import crypto from 'crypto';
// polyfill for jsdom
global.TextEncoder = TextEncoder;
// @ts-expect-error "Cannot assign to 'subtle' because it is a read-only property."
global.crypto.subtle = crypto.webcrypto.subtle;

/* eslint-disable import/first */
import { Address } from 'web3-types';
import { Web3ValidatorError, isHexStrict } from 'web3-validator';
import {
create,
decrypt,
encrypt,
hashMessage,
privateKeyToAccount,
privateKeyToAddress,
recover,
recoverTransaction,
sign,
signTransaction,
privateKeyToPublicKey,
} from '../../src/account';
import {
invalidDecryptData,
invalidEncryptData,
invalidKeyStore,
invalidPrivateKeytoAccountData,
invalidPrivateKeyToAddressData,
signatureRecoverData,
transactionsTestData,
validDecryptData,
validEncryptData,
validHashMessageData,
validPrivateKeytoAccountData,
validPrivateKeyToAddressData,
validPrivateKeyToPublicKeyData,
validRecover,
} from '../fixtures/account';
import { TransactionFactory } from '../../src/tx/transactionFactory';
import { TxData } from '../../src/tx/types';

describe('accounts', () => {
describe('create', () => {
describe('valid cases', () => {
it('%s', () => {
const account = create();
expect(typeof account.privateKey).toBe('string');
expect(typeof account.address).toBe('string');
expect(isHexStrict(account.address)).toBe(true);
expect(typeof account.encrypt).toBe('function');
expect(typeof account.sign).toBe('function');
expect(typeof account.signTransaction).toBe('function');
});
});
});

describe('privateKeyToAddress', () => {
describe('valid cases', () => {
it.each(validPrivateKeyToAddressData)('%s', (input, output) => {
expect(privateKeyToAddress(input)).toEqual(output);
});
});

describe('invalid cases', () => {
it.each(invalidPrivateKeyToAddressData)('%s', (input, output) => {
expect(() => privateKeyToAddress(input)).toThrow(output);
});
});
});

describe('privateKeyToAccount', () => {
describe('valid cases', () => {
it.each(validPrivateKeytoAccountData)('%s', (input, output) => {
expect(JSON.stringify(privateKeyToAccount(input.address, input.ignoreLength))).toEqual(
JSON.stringify(output),
);
});
});

describe('invalid cases', () => {
it.each(invalidPrivateKeytoAccountData)('%s', (input, output) => {
expect(() => privateKeyToAccount(input)).toThrow(output);
});
});
});
describe('privateKeyToPublicKey', () => {
describe('valid cases', () => {
it.each(validPrivateKeyToPublicKeyData)('%s', (privateKey, isCompressed, output) => {
expect(privateKeyToPublicKey(privateKey, isCompressed)).toEqual(output);
});
});
});

describe('Signing and Recovery of Transaction', () => {
it.each(transactionsTestData)('sign transaction', async txData => {
const account = create();

const signedResult = await signTransaction(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
TransactionFactory.fromTxData(txData as unknown as TxData),
account.privateKey,
);
expect(signedResult).toBeDefined();
expect(signedResult.messageHash).toBeDefined();
expect(signedResult.rawTransaction).toBeDefined();
expect(signedResult.transactionHash).toBeDefined();
expect(signedResult.r).toMatch(/0[xX][0-9a-fA-F]{64}/);
expect(signedResult.s).toMatch(/0[xX][0-9a-fA-F]{64}/);
expect(signedResult.v).toMatch(/0[xX][0-9a-fA-F]+/);
});

it.each(transactionsTestData)('Recover transaction', async txData => {
const account = create();
const txObj = { ...txData, from: account.address };
const signedResult = await signTransaction(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
TransactionFactory.fromTxData(txObj),
account.privateKey,
);
expect(signedResult).toBeDefined();

const address: Address = recoverTransaction(signedResult.rawTransaction);
expect(address).toBeDefined();
expect(address).toEqual(account.address);
});
});

describe('Hash Message', () => {
it.each(validHashMessageData)('%s', (message, hash) => {
expect(hashMessage(message)).toEqual(hash);
});
});

describe('Sign Message', () => {
describe('sign', () => {
it.each(signatureRecoverData)('%s', (data, testObj) => {
const result = sign(data, testObj.privateKey);
expect(result.signature).toEqual(testObj.signature || testObj.signatureOrV); // makes sure we get signature and not V value
expect(result.r).toEqual(testObj.r);
expect(result.s).toEqual(testObj.s);
});
});

describe('recover', () => {
it.each(signatureRecoverData)('%s', (data, testObj) => {
const address = recover(data, testObj.signatureOrV, testObj.prefixedOrR, testObj.s);
expect(address).toEqual(testObj.address);
});
});
});

describe('encrypt', () => {
describe('valid cases', () => {
it.each(validEncryptData)('%s', async (input, output) => {
const result = await encrypt(input[0], input[1], input[2]).catch(err => {
throw err;
});
expect(result.version).toBe(output.version);
expect(result.address).toBe(output.address);
expect(result.crypto.ciphertext).toBe(output.crypto.ciphertext);
expect(result.crypto.cipherparams).toEqual(output.crypto.cipherparams);
expect(result.crypto.cipher).toEqual(output.crypto.cipher);
expect(result.crypto.kdf).toBe(output.crypto.kdf);
expect(result.crypto.kdfparams).toEqual(output.crypto.kdfparams);
expect(typeof result.version).toBe('number');
expect(typeof result.id).toBe('string');
expect(typeof result.crypto.mac).toBe('string');
});
});

describe('invalid cases', () => {
it.each(invalidEncryptData)('%s', async (input, output) => {
const result = encrypt(input[0], input[1], input[2]);
await expect(result).rejects.toThrow(output);
});
});
});

describe('decrypt', () => {
describe('valid cases', () => {
it.each(validDecryptData)('%s', async input => {
const keystore = await encrypt(input[0], input[1], input[2]).catch(err => {
throw err;
});

// make sure decrypt does not throw invalid password error
const result = await decrypt(keystore, input[1]);

expect(JSON.stringify(result)).toEqual(JSON.stringify(privateKeyToAccount(input[3])));

const keystoreString = JSON.stringify(keystore);

const stringResult = await decrypt(keystoreString, input[1], true);

expect(JSON.stringify(stringResult)).toEqual(JSON.stringify(privateKeyToAccount(input[3])));
});
});

describe('invalid cases', () => {
it.each(invalidDecryptData)('%s', async (input, output) => {
const result = decrypt(input[0], input[1]);

await expect(result).rejects.toThrow(output);
});
});

describe('invalid keystore, fails validation', () => {
it.each(invalidKeyStore)('%s', async input => {
const result = decrypt(input[0], input[1]);

await expect(result).rejects.toThrow(Web3ValidatorError);
});
});

describe('valid signatures for recover', () => {
it.each(validRecover)('&s', (data, signature) => {
recover(data, signature);
});
});
});
});
2 changes: 2 additions & 0 deletions packages/web3-utils/src/uint8array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export function isUint8Array(data: unknown | Uint8Array): data is Uint8Array {
(data as { constructor: { name: string } })?.constructor?.name === 'Uint8Array'
);
}

// @TODO: Remove this function and its usages once all sub dependencies uses version 1.3.3 or above of @noble/hashes
export function ensureIfUint8Array<T = any>(data: T) {
if (
!(data instanceof Uint8Array) &&
Expand Down
Loading

1 comment on commit 62c8c85

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 62c8c85 Previous: 6c075db Ratio
processingTx 9625 ops/sec (±3.81%) 9301 ops/sec (±4.81%) 0.97
processingContractDeploy 41354 ops/sec (±6.75%) 39129 ops/sec (±7.62%) 0.95
processingContractMethodSend 20604 ops/sec (±8.39%) 19443 ops/sec (±5.19%) 0.94
processingContractMethodCall 40249 ops/sec (±6.02%) 38971 ops/sec (±6.34%) 0.97
abiEncode 45796 ops/sec (±11.04%) 44252 ops/sec (±6.92%) 0.97
abiDecode 32636 ops/sec (±7.17%) 30419 ops/sec (±8.89%) 0.93
sign 1682 ops/sec (±0.80%) 1656 ops/sec (±4.08%) 0.98
verify 380 ops/sec (±2.97%) 373 ops/sec (±0.78%) 0.98

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.