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

Adapts checksum functionality to RSKIP60 and EIP55 #2727

Merged
merged 8 commits into from
Apr 26, 2019
Merged
Show file tree
Hide file tree
Changes from 6 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
51 changes: 48 additions & 3 deletions docs/_build/html/_sources/web3-utils.rst.txt
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ isAddress

.. code-block:: javascript

web3.utils.isAddress(address)
web3.utils.isAddress(address [, chainId])

Checks if a given string is a valid Ethereum address.
It will also check the checksum, if the address has upper and lowercase letters.
Expand All @@ -418,6 +418,7 @@ Parameters
----------

1. ``address`` - ``String``: An address string.
2. ``chainId`` - ``number`` (optional): Chain id where checksummed address should be valid, defaults to ``null``.
alepc253 marked this conversation as resolved.
Show resolved Hide resolved

-------
Returns
Expand Down Expand Up @@ -446,6 +447,9 @@ Example
web3.utils.isAddress('0xC1912fEE45d61C87Cc5EA59DaE31190FFFFf232d');
> false // wrong checksum

web3.utils.isAddress('0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD', 30);
> true

------------------------------------------------------------------------------


Expand All @@ -454,7 +458,7 @@ toChecksumAddress

.. code-block:: javascript

web3.utils.toChecksumAddress(address)
web3.utils.toChecksumAddress(address [, chainId])

Will convert an upper or lowercase Ethereum address to a checksum address.

Expand All @@ -463,6 +467,7 @@ Parameters
----------

1. ``address`` - ``String``: An address string.
2. ``chainId`` - ``number`` (optional): Chain id where checksummed address should be valid, defaults to ``null``.

-------
Returns
Expand All @@ -482,6 +487,42 @@ Example
web3.utils.toChecksumAddress('0XC1912FEE45D61C87CC5EA59DAE31190FFFFF232D');
> "0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d" // same as above

web3.utils.toChecksumAddress('0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed', 30);
> "0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD"

------------------------------------------------------------------------------


stripHexPrefix
alepc253 marked this conversation as resolved.
Show resolved Hide resolved
=====================

.. code-block:: javascript

web3.utils.stripHexPrefix(address)

Removes prefix from address if exists.

----------
Parameters
----------

1. ``address`` - ``String``: An address string.

-------
Returns
-------

``String``: address without prefix.

-------
Example
-------

.. code-block:: javascript

web3.utils.stripHexPrefix('0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d');
> "c1912fEE45d61C87Cc5EA59DaE31190FFFFf232d"


------------------------------------------------------------------------------

Expand All @@ -491,7 +532,7 @@ checkAddressChecksum

.. code-block:: javascript

web3.utils.checkAddressChecksum(address)
web3.utils.checkAddressChecksum(address [, chainId])

Checks the checksum of a given address. Will also return false on non-checksum addresses.

Expand All @@ -500,6 +541,7 @@ Parameters
----------

1. ``address`` - ``String``: An address string.
2. ``chainId`` - ``number`` (optional): Chain id where checksummed address should be valid, defaults to ``null``.
alepc253 marked this conversation as resolved.
Show resolved Hide resolved

-------
Returns
Expand All @@ -516,6 +558,9 @@ Example
web3.utils.checkAddressChecksum('0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d');
> true

web3.utils.checkAddressChecksum('0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd', 31);
> true


------------------------------------------------------------------------------

Expand Down
42 changes: 29 additions & 13 deletions packages/web3-utils/src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ export const toTwosComplement = (number) => {
*
* @param {String} address the given HEX address
*
* @param {Number} chainId to define checksum behavior
*
* @returns {Boolean}
*/
export const isAddress = (address) => {
export const isAddress = (address, chainId = null) => {
// check if it has the basic requirements of an address
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
return false;
Expand All @@ -107,30 +109,44 @@ export const isAddress = (address) => {
return true;
// Otherwise check each case
} else {
return checkAddressChecksum(address);
return checkAddressChecksum(address, chainId);
}
};

/**
* Removes prefix from address if exists.
*
* @method stripHexPrefix
*
* @param {string} address
*
* @returns {string} address without prefix
*/
export const stripHexPrefix = (str) => {
return str.slice(0, 2) === '0x' ? str.slice(2) : str
}

/**
* Checks if the given string is a checksummed address
*
* @method checkAddressChecksum
*
* @param {String} address the given HEX address
*
* @param {number} chain where checksummed address should be valid.
*
* @returns {Boolean}
*/
export const checkAddressChecksum = (address) => {
// Check each case
address = address.replace(/^0x/i, '');
const addressHash = keccak256(address.toLowerCase()).replace(/^0x/i, '');

for (let i = 0; i < 40; i++) {
// 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])
) {
export const checkAddressChecksum = (address, chainId = null) => {
const stripAddress = stripHexPrefix(address).toLowerCase()
const prefix = chainId != null ? (chainId.toString() + '0x') : ''
const keccakHash = Hash.keccak256(prefix + stripAddress).toString('hex').replace(/^0x/i, '');

for (let i = 0; i < stripAddress.length; i++) {
let output = parseInt(keccakHash[i], 16) >= 8 ?
stripAddress[i].toUpperCase() :
stripAddress[i];
if (stripHexPrefix(address)[i] !== output) {
return false;
}
}
Expand Down
33 changes: 18 additions & 15 deletions packages/web3-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import isString from 'lodash/isString';
import isArray from 'lodash/isArray';
import * as utils from './Utils';
import * as ethjsUnit from 'ethjs-unit';
import Hash from 'eth-lib/lib/hash';

export BN from 'bn.js';
export {soliditySha3} from './SoliditySha3';
Expand Down Expand Up @@ -240,31 +241,32 @@ export const toWei = (number, unit) => {
*
* @method toChecksumAddress
*
* @param {String} address the given HEX address
* @param {string} address the given HEX address
*
* @returns {String}
* @param {number} chain where checksummed address should be valid.
*
* @returns {string} address with checksum applied.
*/
export const toChecksumAddress = (address) => {
if (typeof address === 'undefined') return '';
export const toChecksumAddress = (address, chainId = null) => {
if (typeof address !== 'string') {
return '';
}

if (!/^(0x)?[0-9a-f]{40}$/i.test(address))
throw new Error(`Given address "${address}" is not a valid Ethereum address.`);

address = address.toLowerCase().replace(/^0x/i, '');
const addressHash = utils.keccak256(address).replace(/^0x/i, '');
const stripAddress = stripHexPrefix(address).toLowerCase();
const prefix = chainId != null ? (chainId.toString() + '0x') : '';
const keccakHash = Hash.keccak256(prefix + stripAddress).toString('hex').replace(/^0x/i, '');
let checksumAddress = '0x';

for (let i = 0; i < address.length; i++) {
// If ith character is 9 to f then make it uppercase
if (parseInt(addressHash[i], 16) > 7) {
checksumAddress += address[i].toUpperCase();
} else {
checksumAddress += address[i];
}
}
for (let i = 0; i < stripAddress.length; i++)
checksumAddress += parseInt(keccakHash[i], 16) >= 8 ?
stripAddress[i].toUpperCase() :
stripAddress[i];

return checksumAddress;
};
}

// aliases
export const keccak256 = utils.keccak256;
Expand Down Expand Up @@ -297,3 +299,4 @@ export const isBloom = utils.isBloom;
export const isTopic = utils.isTopic;
export const bytesToHex = utils.bytesToHex;
export const hexToBytes = utils.hexToBytes;
export const stripHexPrefix = utils.stripHexPrefix;
62 changes: 61 additions & 1 deletion packages/web3-utils/tests/src/UtilsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
toUtf8,
toWei,
utf8ToHex,
getSignatureParameters
getSignatureParameters,
toChecksumAddress
} from '../../src';

/**
Expand Down Expand Up @@ -140,6 +141,22 @@ describe('UtilsTest', () => {
});
});

it('calls isAddress with chainId 30 and returns the expected results', () => {
const tests = [
{value: '0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD', is: true},
{value: '0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359', is: true},
{value: '0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB', is: true},
{value: '0xE247a45c287191d435A8a5D72A7C8dc030451E9F', is: false},
{value: '0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB', is: true},
{value: '0xe247a45c287191d435a8a5d72a7c8dc030451e9f', is: true},
{value: '0xE247A45C287191D435A8A5D72A7C8DC030451E9F', is: true}
];

tests.forEach((test) => {
expect(isAddress(test.value, 30)).toEqual(test.is);
});
});

it('calls isBN and returns the expected results', () => {
const tests = [
{
Expand Down Expand Up @@ -181,6 +198,49 @@ describe('UtilsTest', () => {
});
});

it('calls checkAddressChecksum with chainId 31 and returns the expected results', () => {
const tests = [
{value: '0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd', is: true},
{value: '0xFb6916095CA1dF60bb79CE92ce3Ea74C37c5D359', is: true},
{value: '0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB', is: true},
{value: '0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB', is: true},
{value: '0XD1220A0CF47C7B9BE7A2E6BA89F429762E7B9ADB', is: false},
{value: '0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb', is: false},
{value: '0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB', is: false},
{value: '0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', is: false}
];

tests.forEach((test) => {
expect(checkAddressChecksum(test.value, 31)).toEqual(test.is);
});
});

it('calls toChecksumAddress with chainId 30 and returns the expected results', () => {
const tests = [
{value: '0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed', is: '0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD'},
{value: '0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359', is: '0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359'},
{value: '0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb', is: '0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB'},
{value: '0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb', is: '0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB'}
];

tests.forEach((test) => {
expect(toChecksumAddress(test.value, 30)).toEqual(test.is);
});
});

it('calls toChecksumAddress and returns the expected results', () => {
const tests = [
{value: '0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed', is: '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'},
{value: '0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359', is: '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'},
{value: '0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb', is: '0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB'},
{value: '0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb', is: '0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb'}
];

tests.forEach((test) => {
expect(toChecksumAddress(test.value)).toEqual(test.is);
});
});

/* eslint-disable jest/no-identical-title */
describe('calls keccak256', () => {
it('should return keccak256 with hex prefix', () => {
Expand Down
14 changes: 8 additions & 6 deletions packages/web3-utils/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ export function isBN(value: string | number): boolean;
export function isBigNumber(value: BN): boolean;
export function toBN(value: number | string): BN;
export function toTwosComplement(value: number | string | BN): string;
export function isAddress(address: string): boolean;
export function isAddress(address: string, chainId?: number): boolean;
export function isHex(hex: Hex): boolean;
export function isHexStrict(hex: Hex): boolean;
export function asciiToHex(string: string, length?: number): string;
export function hexToAscii(string: string): string;
export function toAscii(string: string): string;
export function bytesToHex(bytes: number[]): string;
export function numberToHex(value: number | string | BN): string;
export function checkAddressChecksum(address: string): boolean;
export function checkAddressChecksum(address: string, chainId?: number): boolean;
export function fromAscii(string: string): string;
export function fromDecimal(value: string | number): string;
export function fromUtf8(string: string): string;
Expand All @@ -100,7 +100,7 @@ export function sha3(value: string | BN): string;
export function randomHex(bytesSize: number): string;
export function utf8ToHex(string: string): string;
export function stringToHex(string: string): string;
export function toChecksumAddress(address: string): string;
export function toChecksumAddress(address: string, chainId?: number): string;
export function toDecimal(hex: Hex): number;
export function toHex(value: number | string | BN): string;
export function toUtf8(string: string): string;
Expand All @@ -115,22 +115,23 @@ export function unitMap(): Units;
export function testAddress(bloom: string, address: string): boolean;
export function testTopic(bloom: string, topic: string): boolean;
export function getSignatureParameters(signature: string): {r: string; s: string; v: number};
export function stripHexPrefix(str: string): string;

// interfaces
export interface Utils {
isBN(value: string | number): boolean;
isBigNumber(value: BN): boolean;
toBN(value: number | string): BN;
toTwosComplement(value: number | string | BN): string;
isAddress(address: string): boolean;
isAddress(address: string, chainId?: number): boolean;
isHex(hex: Hex): boolean;
isHexStrict(hex: Hex): boolean;
asciiToHex(string: string, length?: number): string;
hexToAscii(string: string): string;
toAscii(string: string): string;
bytesToHex(bytes: number[]): string;
numberToHex(value: number | string | BN): string;
checkAddressChecksum(address: string): boolean;
checkAddressChecksum(address: string, chainId?: number): boolean;
fromAscii(string: string): string;
fromDecimal(value: string | number): string;
fromUtf8(string: string): string;
Expand All @@ -149,7 +150,7 @@ export interface Utils {
randomHex(bytesSize: number): string;
utf8ToHex(string: string): string;
stringToHex(string: string): string;
toChecksumAddress(address: string): string;
toChecksumAddress(address: string, chainId?: number): string;
alepc253 marked this conversation as resolved.
Show resolved Hide resolved
toDecimal(hex: Hex): number;
toHex(value: number | string | BN): string;
toUtf8(string: string): string;
Expand All @@ -164,6 +165,7 @@ export interface Utils {
testAddress(bloom: string, address: string): boolean;
testTopic(bloom: string, topic: string): boolean;
getSignatureParameters(signature: string): {r: string; s: string; v: number};
stripHexPrefix(str: string): string;
}

export interface Units {
Expand Down