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

Security: Remove web3 v1.x #420

Merged
merged 19 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16

- uses: actions/checkout@v2

Expand All @@ -34,7 +34,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16

- uses: actions/checkout@v2

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Generate coverage report
uses: actions/setup-node@v1
with:
node-version: '12'
node-version: '16'
- run: npm install
- run: npm run run-coverage
- run: npm run generate-report
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16

- uses: actions/checkout@v2
- name: Build
Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,20 @@ on:

jobs:
unit:
name: Unit Test (Node.js v${{ matrix.node }})
name: Unit Test (Node.js v16)
runs-on: ubuntu-latest
strategy:
matrix:
node: [10, 11, 12, 13]

steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
node-version: 16

- uses: actions/checkout@v2

- uses: actions/cache@v2
id: cache-deps
with:
path: node_modules
key: ${{ runner.os }}-node${{ matrix.code }}-${{ hashFiles('**/package-lock.json') }}
key: ${{ runner.os }}-node16-${{ hashFiles('**/package-lock.json') }}

- name: Run test
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 14
node-version: 16

- uses: actions/cache@v2
id: cache-deps
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_e2e_IBC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 14
node-version: 16

- uses: actions/cache@v2
id: cache-deps
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/upload.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16

- uses: actions/checkout@v2
- name: build lib
Expand Down
2 changes: 1 addition & 1 deletion lib/src/transaction/v2.raw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ describe('Transaction', function () {
expect(() => {
anyTx.toCosmosJSON();
}).to.throw(
"error converting RawTransaction to Cosmos compatible JSON: TypeError: Cannot read property 'value' of undefined",
"error converting RawTransaction to Cosmos compatible JSON: TypeError: Cannot read properties of undefined (reading 'value')",
);
});

Expand Down
92 changes: 91 additions & 1 deletion lib/src/utils/address.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import 'mocha';
import { expect } from 'chai';
import { AddressType, AddressValidator, validateAddress, getBech32AddressFromEVMAddress } from './address';
import {
AddressType,
AddressValidator,
validateAddress,
getBech32AddressFromEVMAddress,
isHexPrefixed,
isHexStrict,
isUint8Array,
stripHexPrefix,
uint8ArrayToHexString,
isAddress,
checkAddressCheckSum,
} from './address';
import { CroNetwork } from '../core/cro';

describe('Validate address against network and checksums', function () {
Expand Down Expand Up @@ -52,6 +64,84 @@ describe('Validate address against network and checksums', function () {
).to.throw('Invalid checksum for tcrocncl1reyshfdygf7673xm9p8v0xvtd96m6cd6canhu3xcqa');
});

describe('Hex and Bytes conversion checking', function () {
const address = {
tendermint: 'cro1pndm4ywdf4qtmupa0fqe75krmqed2znjyj6x8f',
EVM: '0xD47286f025F947482a2C374Fb70e9D4c94d809CF',
};
it('isHexPrefixed should return false for tendermint address', function () {
expect(isHexPrefixed(address.tendermint)).to.be.eq(false);
});

it('isHexPrefixed should return true for EVM address', function () {
expect(isHexPrefixed(address.EVM)).to.be.eq(true);
});

it('isHexStrict should return true for hexString', function () {
expect(isHexStrict('0xc1912')).to.be.eq(true);
});
it('isHexStrict should return false for hex', function () {
expect(isHexStrict(0xc1912)).to.be.eq(false);
});
it('isHexStrict should return false for normal string', function () {
expect(isHexStrict('c1912')).to.be.eq(false);
});
it('isHexStrict should return false for number', function () {
expect(isHexStrict(345)).to.be.eq(false);
});
it('isUint8Array should return true for Uint8Array instance', function () {
expect(isUint8Array(new Uint8Array([21, 31]))).to.be.eq(true);
});
it('isUint8Array should return true for Buffer instance', function () {
expect(isUint8Array(Buffer.from([21, 31]))).to.be.eq(true);
});
it('isUint8Array should return false for string', function () {
expect(isUint8Array('string')).to.be.eq(false);
});
it(`stripHexPrefix should return ${address.tendermint} without modification`, function () {
expect(stripHexPrefix(address.tendermint)).to.be.eq(address.tendermint);
});
it(`stripHexPrefix should trim '0x' from ${address.EVM}`, function () {
expect(stripHexPrefix(address.EVM)).to.be.eq('D47286f025F947482a2C374Fb70e9D4c94d809CF');
});
it(`uint8ArrayToHexString convert empty Uint8Array to HexString`, function () {
expect(uint8ArrayToHexString(new Uint8Array(4))).to.be.eq('0x00000000');
});
it(`uint8ArrayToHexString convert Uint8Array to HexString`, function () {
expect(uint8ArrayToHexString(new Uint8Array([0x1f, 0x2f, 0x3f, 0x4f]))).to.be.eq('0x1f2f3f4f');
});
it(`isAddress should return false for valid EVM address`, function () {
expect(isAddress(address.tendermint)).to.be.eq(false);
});
it(`isAddress should return true for valid EVM address`, function () {
expect(isAddress(address.EVM)).to.be.eq(true);
});
it(`isAddress should return true with EVM-valid Uint8Array`, function () {
expect(isAddress(new Uint8Array(20))).to.be.eq(true);
});
it(`isAddress should return false for non 40-length string`, function () {
expect(isAddress('should return false')).to.be.eq(false);
});
it(`isAddress should return false for 40-length non EVM-valid string`, function () {
expect(isAddress('0xD47286f025F947482a2C374Fb70e9D4c94d809CG')).to.be.eq(false);
});
it(`isAddress should return true for valid capitalized EVM address`, function () {
expect(isAddress(address.EVM.toUpperCase())).to.be.eq(true);
});
it(`isAddress should return true for valid trimmed EVM address`, function () {
expect(isAddress(stripHexPrefix(address.EVM))).to.be.eq(true);
});
it(`checkAddressCheckSum should return true for valid EVM address`, function () {
expect(checkAddressCheckSum(address.EVM)).to.be.eq(true);
});
it(`checkAddressCheckSum should return false for invalid EVM address`, function () {
expect(checkAddressCheckSum('0x6c46a1e212f127a6a8787b456a243c0d')).to.be.eq(false);
});
it(`checkAddressCheckSum should return false`, function () {
expect(checkAddressCheckSum('0xd47286f025F947482a2C374Fb70e9D4c94d809CF')).to.be.eq(false);
});
});

describe('AddressValidator', function () {
it('validate should throw Error when the address is invalid', function () {
const addressProps = {
Expand Down
81 changes: 72 additions & 9 deletions lib/src/utils/address.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import bech32 from 'bech32';
import { isAddress, stripHexPrefix } from 'web3-utils';
import { keccak256 } from 'ethereum-cryptography/keccak';
import { utf8ToBytes } from 'ethereum-cryptography/utils';
import { Network } from '../network/network';
import { Bytes } from './bytes/bytes';
// import { toBase64 } from '@cosmjs/encoding';

export interface AddressValidationProperties {
address: string;
Expand All @@ -14,13 +14,76 @@ export enum AddressType {
USER,
VALIDATOR,
}
// https://stackoverflow.com/questions/49434751/how-to-declare-a-function-that-throws-an-error-in-typescript
/**
* Check address validity against its type and provided network
* @param {AddressValidationProperties} addressProps
* @returns {boolean}
* @throws {Error} when Bech32 encoding is not correct
*/

export function isHexPrefixed(str: string): boolean {
return str.startsWith('0x');
}

export function stripHexPrefix(str: string): string {
return isHexPrefixed(str) ? str.slice(2) : str;
}

export function isUint8Array(data: unknown | Uint8Array): data is Uint8Array {
return (
data instanceof Uint8Array ||
(data as { constructor: { name: string } })?.constructor?.name === 'Uint8Array' ||
(data as { constructor: { name: string } })?.constructor?.name === 'Buffer'
);
}

export function uint8ArrayToHexString(uint8Array: Uint8Array): string {
return uint8Array.reduce((hexString, e) => {
const hex = e.toString(16);
return hexString + (hex.length === 1 ? `0${hex}` : hex);
}, '0x');
}

export function isHexStrict(hex: Uint8Array | bigint | string | number | boolean) {
return typeof hex === 'string' && /^((-)?0x[0-9a-f]+|(0x))$/i.test(hex);
}

export function checkAddressCheckSum(data: string): boolean {
if (!/^(0x)?[0-9a-f]{40}$/i.test(data)) return false;
const address = data.slice(2);
const updatedData = utf8ToBytes(address.toLowerCase());

const addressHash = uint8ArrayToHexString(keccak256(updatedData)).slice(2);

for (let i = 0; i < 40; i += 1) {
// the nth letter should be uppercase if the nth digit of casemap is 1
if (
(parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) ||
(parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])
) {
return false;
}
}
return true;
}

export const isAddress = (value: Uint8Array | string, checkChecksum = true) => {
let valueToCheck: string;

if (isUint8Array(value)) {
valueToCheck = uint8ArrayToHexString(value);
} else if (typeof value === 'string' && !isHexStrict(value)) {
valueToCheck = value.toLowerCase().startsWith('0x') ? value : `0x${value}`;
} else {
valueToCheck = value;
}

// check if it has the basic requirements of an address
if (!/^(0x)?[0-9a-f]{40}$/i.test(valueToCheck)) {
return false;
}
// If it's ALL lowercase or ALL upppercase
if (/^(0x|0X)?[0-9a-f]{40}$/.test(valueToCheck) || /^(0x|0X)?[0-9A-F]{40}$/.test(valueToCheck)) {
return true;
// Otherwise check each case
}
return checkChecksum ? checkAddressCheckSum(valueToCheck) : true;
};

export function validateAddress(addressProps: AddressValidationProperties): boolean | never {
const { network } = addressProps;
const bech32Decoded = bech32.decode(addressProps.address);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/utils/txDecoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('TxDecoder', function () {
const txDecoder = new TxDecoder();
const txBytes = Bytes.fromBase64String('CpQBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKK3Rjcm8xMnlnd2R2ZnZndDRjNzJlMG11N2g2Z21mdjl5d2gzNHI5a2FjanISK3Rjcm8xMnlnd2R2ZnZndDRjNzJlMG11N2g2Z21mdjl5d2gzNHI5a2FjanIaFQoIYmFzZXRjcm8SCTEwMDAwMDAwMBKxAgqoAgqIAgopL2Nvc21vcy5jcnlwdG8ubXVsdGlzaWcuTGVnYWN5QW1pbm9QdWJLZXkS2gEIAxJGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQMmHiFA8uJvK1ug4G0W1/pPLiZ+Ora8MsrgRPO9ZUbAxBJGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQIXveFFPdAc68u/wp8cyiSeVxSSaieLvHDr/a6ut9gf2RJGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQILzYXwxGx61Az+IAaYhDpsTKIPgRwhIOEgePSj1Ae5vhIbEhkKBQgDEgHgEgQKAgh/EgQKAgh/EgQKAgh/EgQQwJoMGsYBCkAqnZ+kKTI2KNThqP4bi67jdF4vUItthnQjzzUbbpVrNS1L1JzRKAk8p3JAD/ZcJv5NrYH6nj/XA3BIY5aDGORRCkC+o5tK8zr8OZLuFIwias8t7v2U6u8XXrfNFL6uF3TyBSpvmW8BwCRZDFkwKosz6ryg6rObF6NCpheN0t+e7j+UCkCntQCqbypaLXA8RD0o7B/Gb5iQqD5jpOR0hd7rVQZ1xm+g6bKXS6Vd+vpNlzXmCUD1h8AxgEkKWxN5cQzL/0ZW');

expect(() => txDecoder.fromHex(txBytes.toHexString()).toCosmosJSON()).to.throw("Cannot read property 'length' of undefined");
expect(() => txDecoder.fromHex(txBytes.toHexString()).toCosmosJSON()).to.throw("Cannot read properties of undefined (reading 'length')");
});

it('should throw on invalid tx body messages array', function () {
Expand Down
Loading
Loading