From 4d8a802c49c1aa71031ef3017fd2e13206ca7bab Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 6 Jul 2023 14:43:02 +0200 Subject: [PATCH 1/9] feat: add isValidHexAddress function --- src/hex.test.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/hex.ts | 24 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/hex.test.ts b/src/hex.test.ts index 5fcc8a38b..3d60547a6 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -4,6 +4,7 @@ import { assertIsStrictHexString, isHexString, isStrictHexString, + isValidHexAddress, remove0x, } from './hex'; @@ -151,6 +152,54 @@ describe('assertIsStrictHexString', () => { }); }); +describe('isValidHexAddress', () => { + describe('with allowNonPrefixed option set to true', () => { + it.each([ + '0000000000000000000000000000000000000000', + 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + '0x0000000000000000000000000000000000000000', + ])('returns true for a valid prefixed hex address', (hexString) => { + expect(isValidHexAddress(hexString)).toBe(true); + }); + + it.each([ + '0000000000000000000000000000000000000000', + 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + ])('returns true for a valid non-prefixed hex address', (hexString) => { + expect(isValidHexAddress(hexString)).toBe(true); + }); + }); + + describe('with allowNonPrefixed option set to false', () => { + it.each([ + '0000000000000000000000000000000000000000', + 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + ])('returns false for a valid non-prefixed hex address', (hexString) => { + expect(isValidHexAddress(hexString, { allowNonPrefixed: false })).toBe( + false, + ); + }); + }); + + it.each([ + '12345g', + '1234567890abcdefg', + '1234567890abcdefG', + '1234567890abcdefABCDEFg', + '1234567890abcdefABCDEF1234567890abcdefABCDEFg', + '0x', + '0x0', + '0x12345g', + '0x1234567890abcdefg', + '0x1234567890abcdefG', + '0x1234567890abcdefABCDEFg', + '0x1234567890abcdefABCDEF1234567890abcdefABCDEFg', + ])('returns false for an invalid hex address', (hexString) => { + expect(isValidHexAddress(hexString)).toBe(false); + }); +}); + describe('add0x', () => { it('adds a 0x-prefix to a string', () => { expect(add0x('12345')).toBe('0x12345'); diff --git a/src/hex.ts b/src/hex.ts index 9cb4d92d7..42dda65c5 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -55,6 +55,30 @@ export function assertIsStrictHexString(value: unknown): asserts value is Hex { ); } +/** + * Validates that the input is a hex address. This utility by default + * will return true for hex strings that meet the length requirement + * of a hex address, but are not necessarily prefixed with `0x`. + * + * @param possibleAddress - Input parameter to check against. + * @param options - The validation options. + * @param options.allowNonPrefixed - If true will allow addresses without `0x` prefix.` + * @returns Whether or not the input is a valid hex address. + */ +export function isValidHexAddress( + possibleAddress: string, + { allowNonPrefixed = true } = {}, +) { + const addressToCheck = allowNonPrefixed + ? add0x(possibleAddress) + : possibleAddress; + if (!isHexString(addressToCheck)) { + return false; + } + + return is(addressToCheck, pattern(string(), /^0x[0-9a-fA-F]{40}$/u)); +} + /** * Add the `0x`-prefix to a hexadecimal string. If the string already has the * prefix, it is returned as-is. From 4902357ac3f1c79ba56a9082e903147a7d11229e Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 6 Jul 2023 15:05:55 +0200 Subject: [PATCH 2/9] fix: accept only prefixed addresses --- src/hex.test.ts | 38 +++++++++++++------------------------- src/hex.ts | 24 +++++++----------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/src/hex.test.ts b/src/hex.test.ts index 3d60547a6..328b9e546 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -1,4 +1,5 @@ import { + Hex, add0x, assertIsHexString, assertIsStrictHexString, @@ -153,33 +154,19 @@ describe('assertIsStrictHexString', () => { }); describe('isValidHexAddress', () => { - describe('with allowNonPrefixed option set to true', () => { - it.each([ - '0000000000000000000000000000000000000000', - 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - '0x0000000000000000000000000000000000000000', - ])('returns true for a valid prefixed hex address', (hexString) => { - expect(isValidHexAddress(hexString)).toBe(true); - }); - - it.each([ - '0000000000000000000000000000000000000000', - 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - ])('returns true for a valid non-prefixed hex address', (hexString) => { - expect(isValidHexAddress(hexString)).toBe(true); - }); + it.each([ + '0x0000000000000000000000000000000000000000' as Hex, + '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' as Hex, + ])('returns true for a valid prefixed hex address', (hexString) => { + expect(isValidHexAddress(hexString)).toBe(true); }); - describe('with allowNonPrefixed option set to false', () => { - it.each([ - '0000000000000000000000000000000000000000', - 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - ])('returns false for a valid non-prefixed hex address', (hexString) => { - expect(isValidHexAddress(hexString, { allowNonPrefixed: false })).toBe( - false, - ); - }); + it.each([ + '0000000000000000000000000000000000000000', + 'd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + ])('returns false for a valid non-prefixed hex address', (hexString) => { + // @ts-expect-error - testing invalid input + expect(isValidHexAddress(hexString)).toBe(false); }); it.each([ @@ -196,6 +183,7 @@ describe('isValidHexAddress', () => { '0x1234567890abcdefABCDEFg', '0x1234567890abcdefABCDEF1234567890abcdefABCDEFg', ])('returns false for an invalid hex address', (hexString) => { + // @ts-expect-error - testing invalid input expect(isValidHexAddress(hexString)).toBe(false); }); }); diff --git a/src/hex.ts b/src/hex.ts index 42dda65c5..b0e955af2 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -9,6 +9,10 @@ export const StrictHexStruct = pattern(string(), /^0x[0-9a-f]+$/iu) as Struct< Hex, null >; +export const HexAddressStruct = pattern( + string(), + /^0x[0-9a-f]{40}$/iu, +) as Struct; /** * Check if a string is a valid hex string. @@ -56,27 +60,13 @@ export function assertIsStrictHexString(value: unknown): asserts value is Hex { } /** - * Validates that the input is a hex address. This utility by default - * will return true for hex strings that meet the length requirement - * of a hex address, but are not necessarily prefixed with `0x`. + * Validates that the passed prefixed hex string is a correct hex address. * * @param possibleAddress - Input parameter to check against. - * @param options - The validation options. - * @param options.allowNonPrefixed - If true will allow addresses without `0x` prefix.` * @returns Whether or not the input is a valid hex address. */ -export function isValidHexAddress( - possibleAddress: string, - { allowNonPrefixed = true } = {}, -) { - const addressToCheck = allowNonPrefixed - ? add0x(possibleAddress) - : possibleAddress; - if (!isHexString(addressToCheck)) { - return false; - } - - return is(addressToCheck, pattern(string(), /^0x[0-9a-fA-F]{40}$/u)); +export function isValidHexAddress(possibleAddress: Hex) { + return is(possibleAddress, HexAddressStruct); } /** From 763a25b6a1ad3ba99157bcdb90e19b778eaa8b1a Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 6 Jul 2023 15:14:31 +0200 Subject: [PATCH 3/9] fix: remove case insensitive flag --- src/hex.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hex.ts b/src/hex.ts index b0e955af2..331be1821 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -11,7 +11,7 @@ export const StrictHexStruct = pattern(string(), /^0x[0-9a-f]+$/iu) as Struct< >; export const HexAddressStruct = pattern( string(), - /^0x[0-9a-f]{40}$/iu, + /^0x[0-9a-fA-F]{40}$/u, ) as Struct; /** From d421bb77e2af8f53879c65f70dfbcca943144c17 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Thu, 6 Jul 2023 15:42:10 +0200 Subject: [PATCH 4/9] test: add test case for 0X --- src/hex.test.ts | 1 + src/hex.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hex.test.ts b/src/hex.test.ts index 328b9e546..efac6dfba 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -182,6 +182,7 @@ describe('isValidHexAddress', () => { '0x1234567890abcdefG', '0x1234567890abcdefABCDEFg', '0x1234567890abcdefABCDEF1234567890abcdefABCDEFg', + '0Xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', ])('returns false for an invalid hex address', (hexString) => { // @ts-expect-error - testing invalid input expect(isValidHexAddress(hexString)).toBe(false); diff --git a/src/hex.ts b/src/hex.ts index 331be1821..dbe30a39c 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -60,7 +60,7 @@ export function assertIsStrictHexString(value: unknown): asserts value is Hex { } /** - * Validates that the passed prefixed hex string is a correct hex address. + * Validates that the passed prefixed hex string is a valid hex address. * * @param possibleAddress - Input parameter to check against. * @returns Whether or not the input is a valid hex address. From 324508935d929223b26b92d87fa5ed54dcfc8312 Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Mon, 10 Jul 2023 18:30:00 +0200 Subject: [PATCH 5/9] feat: validate checksum addresses --- package.json | 1 + src/hex.test.ts | 23 +++++++++++++++++++++++ src/hex.ts | 44 ++++++++++++++++++++++++++++++++++++++++++-- yarn.lock | 10 +++++++++- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7eb26aa85..80794aceb 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@ethereumjs/tx": "^4.1.2", + "@noble/hashes": "^1.3.1", "@types/debug": "^4.1.7", "debug": "^4.3.4", "semver": "^7.3.8", diff --git a/src/hex.test.ts b/src/hex.test.ts index efac6dfba..525388e2a 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -3,6 +3,7 @@ import { add0x, assertIsHexString, assertIsStrictHexString, + isValidChecksumAddress, isHexString, isStrictHexString, isValidHexAddress, @@ -182,6 +183,8 @@ describe('isValidHexAddress', () => { '0x1234567890abcdefG', '0x1234567890abcdefABCDEFg', '0x1234567890abcdefABCDEF1234567890abcdefABCDEFg', + '0xD8DA6BF26964AF9D7EED9E03E53415D37AA96045', + '0xCF5609B003B2776699EEA1233F7C82D5695CC9AA', '0Xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', ])('returns false for an invalid hex address', (hexString) => { // @ts-expect-error - testing invalid input @@ -189,6 +192,26 @@ describe('isValidHexAddress', () => { }); }); +describe('isValidChecksumAddress', () => { + it.each([ + '0x0000000000000000000000000000000000000000' as Hex, + '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' as Hex, + '0xCf5609B003B2776699eEA1233F7C82D5695cC9AA' as Hex, + '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' as Hex, + '0x8617E340B3D01FA5F11F306F4090FD50E238070D' as Hex, + ])('returns true for a valid checksum address', (hexString) => { + expect(isValidChecksumAddress(hexString)).toBe(true); + }); + + it.each([ + '0xz' as Hex, + '0xD8DA6BF26964AF9D7EED9E03E53415D37AA96045' as Hex, + '0xCF5609B003B2776699EEA1233F7C82D5695CC9AA' as Hex, + ])('returns false for an invalid checksum address', (hexString) => { + expect(isValidChecksumAddress(hexString)).toBe(false); + }); +}); + describe('add0x', () => { it('adds a 0x-prefix to a string', () => { expect(add0x('12345')).toBe('0x12345'); diff --git a/src/hex.ts b/src/hex.ts index dbe30a39c..cbd3bfdab 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -1,6 +1,8 @@ +import { keccak_256 as keccak256 } from '@noble/hashes/sha3'; import { is, pattern, string, Struct } from 'superstruct'; import { assert } from './assert'; +import { bytesToHex } from './bytes'; export type Hex = `0x${string}`; @@ -10,6 +12,10 @@ export const StrictHexStruct = pattern(string(), /^0x[0-9a-f]+$/iu) as Struct< null >; export const HexAddressStruct = pattern( + string(), + /^0x[0-9a-f]{40}$/u, +) as Struct; +export const HexChecksumAddressStruct = pattern( string(), /^0x[0-9a-fA-F]{40}$/u, ) as Struct; @@ -60,13 +66,47 @@ export function assertIsStrictHexString(value: unknown): asserts value is Hex { } /** - * Validates that the passed prefixed hex string is a valid hex address. + * Validate that the passed prefixed hex string is a valid hex address, or a + * valid mixed-case checksum address. * * @param possibleAddress - Input parameter to check against. * @returns Whether or not the input is a valid hex address. */ export function isValidHexAddress(possibleAddress: Hex) { - return is(possibleAddress, HexAddressStruct); + return ( + is(possibleAddress, HexAddressStruct) || + isValidChecksumAddress(possibleAddress) + ); +} + +/** + * Validate that the passed hex string is a valid ERC-55 mixed-case + * checksum address. + * + * @param possibleChecksum - The hex address to check. + * @returns True if the address is a checksum address. + */ +export function isValidChecksumAddress(possibleChecksum: Hex) { + if (!is(possibleChecksum, HexChecksumAddressStruct)) { + return false; + } + + const unPrefixed = remove0x(possibleChecksum); + const unPrefixedHash = remove0x( + bytesToHex(keccak256(unPrefixed.toLowerCase())), + ); + + for (let i = 0; i < unPrefixedHash.length; i++) { + const value = parseInt(unPrefixedHash[i] as string, 16); + if ( + (value > 7 && unPrefixed[i]?.toUpperCase() !== unPrefixed[i]) || + (value <= 7 && unPrefixed[i]?.toLowerCase() !== unPrefixed[i]) + ) { + return false; + } + } + + return true; } /** diff --git a/yarn.lock b/yarn.lock index 92902c418..b9ee57cc4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1069,6 +1069,7 @@ __metadata: "@metamask/eslint-config-jest": ^11.0.0 "@metamask/eslint-config-nodejs": ^11.0.1 "@metamask/eslint-config-typescript": ^11.0.0 + "@noble/hashes": ^1.3.1 "@types/debug": ^4.1.7 "@types/jest": ^28.1.7 "@types/node": ^17.0.23 @@ -1108,13 +1109,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.0, @noble/hashes@npm:^1.3.0, @noble/hashes@npm:~1.3.0": +"@noble/hashes@npm:1.3.0": version: 1.3.0 resolution: "@noble/hashes@npm:1.3.0" checksum: d7ddb6d7c60f1ce1f87facbbef5b724cdea536fc9e7f59ae96e0fc9de96c8f1a2ae2bdedbce10f7dcc621338dfef8533daa73c873f2b5c87fa1a4e05a95c2e2e languageName: node linkType: hard +"@noble/hashes@npm:^1.3.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0": + version: 1.3.1 + resolution: "@noble/hashes@npm:1.3.1" + checksum: 7fdefc0f7a0c1ec27acc6ff88841793e3f93ec4ce6b8a6a12bfc0dd70ae6b7c4c82fe305fdfeda1735d5ad4a9eebe761e6693b3d355689c559e91242f4bc95b1 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" From 58ae2d2aeab5ee84a89a78363696687bf103edc6 Mon Sep 17 00:00:00 2001 From: legobeat <109787230+legobeat@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:43:12 +0000 Subject: [PATCH 6/9] feat: break out address checksum encoding to erc55EncodeAddress function (#113) * feat: break out address checksum encoding to erc55EncodeAddress function * deps: ethereum-cryptography@2.0.0->2.1.0; dedupe @noble/hashes * Update jsdoc Co-authored-by: Maarten Zuidhoorn * refactor: rename erc55EncodeAddress function * test: add test cases for getChecksumAddress --------- Co-authored-by: Maarten Zuidhoorn Co-authored-by: Michele Esposito --- src/hex.test.ts | 25 +++++++++++++++++++++++ src/hex.ts | 37 +++++++++++++++++++--------------- yarn.lock | 53 +++++++++++++++++++++---------------------------- 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/hex.test.ts b/src/hex.test.ts index 525388e2a..13abaeb2b 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -8,6 +8,7 @@ import { isStrictHexString, isValidHexAddress, remove0x, + getChecksumAddress, } from './hex'; describe('isHexString', () => { @@ -192,6 +193,30 @@ describe('isValidHexAddress', () => { }); }); +describe('getChecksumAddress', () => { + it('returns the checksum address for a valid hex address', () => { + expect( + getChecksumAddress('0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed'), + ).toBe('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'); + + expect( + getChecksumAddress('0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359'), + ).toBe('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'); + + expect( + getChecksumAddress('0x52908400098527886e0f7030069857d2e4169ee7'), + ).toBe('0x52908400098527886E0F7030069857D2E4169EE7'); + + expect( + getChecksumAddress('0xde709f2102306220921060314715629080e2fb77'), + ).toBe('0xde709f2102306220921060314715629080e2fb77'); + + expect( + getChecksumAddress('0x0000000000000000000000000000000000000000'), + ).toBe('0x0000000000000000000000000000000000000000'); + }); +}); + describe('isValidChecksumAddress', () => { it.each([ '0x0000000000000000000000000000000000000000' as Hex, diff --git a/src/hex.ts b/src/hex.ts index cbd3bfdab..0373992ff 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -79,6 +79,26 @@ export function isValidHexAddress(possibleAddress: Hex) { ); } +/** + * Encode a passed hex string as an ERC-55 mixed-case checksum address. + * + * @param address - The hex address to encode. + * @returns The address encoded according to ERC-55. + * @see https://eips.ethereum.org/EIPS/eip-55 + */ +export function getChecksumAddress(address: Hex) { + const unPrefixed = remove0x(address.toLowerCase()); + const unPrefixedHash = remove0x(bytesToHex(keccak256(unPrefixed))); + return `0x${unPrefixed + .split('') + .map((character, nibbleIndex) => + parseInt(unPrefixedHash[nibbleIndex] as string, 16) > 7 + ? character.toUpperCase() + : character, + ) + .join('')}`; +} + /** * Validate that the passed hex string is a valid ERC-55 mixed-case * checksum address. @@ -91,22 +111,7 @@ export function isValidChecksumAddress(possibleChecksum: Hex) { return false; } - const unPrefixed = remove0x(possibleChecksum); - const unPrefixedHash = remove0x( - bytesToHex(keccak256(unPrefixed.toLowerCase())), - ); - - for (let i = 0; i < unPrefixedHash.length; i++) { - const value = parseInt(unPrefixedHash[i] as string, 16); - if ( - (value > 7 && unPrefixed[i]?.toUpperCase() !== unPrefixed[i]) || - (value <= 7 && unPrefixed[i]?.toLowerCase() !== unPrefixed[i]) - ) { - return false; - } - } - - return true; + return getChecksumAddress(possibleChecksum) === possibleChecksum; } /** diff --git a/yarn.lock b/yarn.lock index b9ee57cc4..1cdcb33d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1100,23 +1100,16 @@ __metadata: languageName: unknown linkType: soft -"@noble/curves@npm:1.0.0, @noble/curves@npm:~1.0.0": - version: 1.0.0 - resolution: "@noble/curves@npm:1.0.0" +"@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": + version: 1.1.0 + resolution: "@noble/curves@npm:1.1.0" dependencies: - "@noble/hashes": 1.3.0 - checksum: 6bcef44d626c640dc8961819d68dd67dffb907e3b973b7c27efe0ecdd9a5c6ce62c7b9e3dfc930c66605dced7f1ec0514d191c09a2ce98d6d52b66e3315ffa79 + "@noble/hashes": 1.3.1 + checksum: 2658cdd3f84f71079b4e3516c47559d22cf4b55c23ac8ee9d2b1f8e5b72916d9689e59820e0f9d9cb4a46a8423af5b56dc6bb7782405c88be06a015180508db5 languageName: node linkType: hard -"@noble/hashes@npm:1.3.0": - version: 1.3.0 - resolution: "@noble/hashes@npm:1.3.0" - checksum: d7ddb6d7c60f1ce1f87facbbef5b724cdea536fc9e7f59ae96e0fc9de96c8f1a2ae2bdedbce10f7dcc621338dfef8533daa73c873f2b5c87fa1a4e05a95c2e2e - languageName: node - linkType: hard - -"@noble/hashes@npm:^1.3.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0": +"@noble/hashes@npm:1.3.1, @noble/hashes@npm:^1.3.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": version: 1.3.1 resolution: "@noble/hashes@npm:1.3.1" checksum: 7fdefc0f7a0c1ec27acc6ff88841793e3f93ec4ce6b8a6a12bfc0dd70ae6b7c4c82fe305fdfeda1735d5ad4a9eebe761e6693b3d355689c559e91242f4bc95b1 @@ -1219,24 +1212,24 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.3.0": - version: 1.3.0 - resolution: "@scure/bip32@npm:1.3.0" +"@scure/bip32@npm:1.3.1": + version: 1.3.1 + resolution: "@scure/bip32@npm:1.3.1" dependencies: - "@noble/curves": ~1.0.0 - "@noble/hashes": ~1.3.0 + "@noble/curves": ~1.1.0 + "@noble/hashes": ~1.3.1 "@scure/base": ~1.1.0 - checksum: 6eae997f9bdf41fe848134898960ac48e645fa10e63d579be965ca331afd0b7c1b8ebac170770d237ab4099dafc35e5a82995384510025ccf2abe669f85e8918 + checksum: 394d65f77a40651eba21a5096da0f4233c3b50d422864751d373fcf142eeedb94a1149f9ab1dbb078086dab2d0bc27e2b1afec8321bf22d4403c7df2fea5bfe2 languageName: node linkType: hard -"@scure/bip39@npm:1.2.0": - version: 1.2.0 - resolution: "@scure/bip39@npm:1.2.0" +"@scure/bip39@npm:1.2.1": + version: 1.2.1 + resolution: "@scure/bip39@npm:1.2.1" dependencies: "@noble/hashes": ~1.3.0 "@scure/base": ~1.1.0 - checksum: 980d761f53e63de04a9e4db840eb13bfb1bd1b664ecb04a71824c12c190f4972fd84146f3ed89b2a8e4c6bd2c17c15f8b592b7ac029e903323b0f9e2dae6916b + checksum: c5bd6f1328fdbeae2dcdd891825b1610225310e5e62a4942714db51066866e4f7bef242c7b06a1b9dcc8043a4a13412cf5c5df76d3b10aa9e36b82e9b6e3eeaa languageName: node linkType: hard @@ -3231,14 +3224,14 @@ __metadata: linkType: hard "ethereum-cryptography@npm:^2.0.0": - version: 2.0.0 - resolution: "ethereum-cryptography@npm:2.0.0" + version: 2.1.0 + resolution: "ethereum-cryptography@npm:2.1.0" dependencies: - "@noble/curves": 1.0.0 - "@noble/hashes": 1.3.0 - "@scure/bip32": 1.3.0 - "@scure/bip39": 1.2.0 - checksum: 958f8aab2d1b32aa759fb27a27877b3647410e8bb9aca7d65d1d477db4864cf7fc46b918eb52a1e246c25e98ee0a35a632c88b496aeaefa13469ee767a76c8db + "@noble/curves": 1.1.0 + "@noble/hashes": 1.3.1 + "@scure/bip32": 1.3.1 + "@scure/bip39": 1.2.1 + checksum: 47bd69103f0553e5c98e0645c295ca74e0da53a92b8d26237287f528521cd2aa13d5cd1e288c36e59ce885451199cef8e4de424a93c45bacf54a06bdd09946a4 languageName: node linkType: hard From 38444a9dd1a7e0b844229e9960c068a135345c0d Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Tue, 11 Jul 2023 17:50:53 +0200 Subject: [PATCH 7/9] docs: edit isValidHexAddress jsdoc description --- src/hex.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hex.ts b/src/hex.ts index 0373992ff..238dfd70c 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -66,8 +66,8 @@ export function assertIsStrictHexString(value: unknown): asserts value is Hex { } /** - * Validate that the passed prefixed hex string is a valid hex address, or a - * valid mixed-case checksum address. + * Validate that the passed prefixed hex string is an all-lowercase + * hex address, or a valid mixed-case checksum address. * * @param possibleAddress - Input parameter to check against. * @returns Whether or not the input is a valid hex address. From 4a49c162cf40c0c036642836c1caa1c825c3ecf6 Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Tue, 11 Jul 2023 18:08:42 +0200 Subject: [PATCH 8/9] Update src/hex.ts Co-authored-by: Mark Stacey --- src/hex.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hex.ts b/src/hex.ts index 238dfd70c..869f2d926 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -91,11 +91,15 @@ export function getChecksumAddress(address: Hex) { const unPrefixedHash = remove0x(bytesToHex(keccak256(unPrefixed))); return `0x${unPrefixed .split('') - .map((character, nibbleIndex) => - parseInt(unPrefixedHash[nibbleIndex] as string, 16) > 7 + .map((character, nibbleIndex) => { + const hashCharacter = unPrefixedHash[nibbleIndex]; + if (!hashCharacter) { + throw new Error('Hash shorter than address'); + } + return parseInt(hashCharacter, 16) > 7 ? character.toUpperCase() - : character, - ) + : character; + }) .join('')}`; } From b907e53171fab8c7c92ae2a7558e9496f5370cea Mon Sep 17 00:00:00 2001 From: Michele Esposito Date: Tue, 11 Jul 2023 18:46:16 +0200 Subject: [PATCH 9/9] fix: add validation assertions --- src/hex.test.ts | 4 ++++ src/hex.ts | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hex.test.ts b/src/hex.test.ts index 13abaeb2b..5e5708363 100644 --- a/src/hex.test.ts +++ b/src/hex.test.ts @@ -215,6 +215,10 @@ describe('getChecksumAddress', () => { getChecksumAddress('0x0000000000000000000000000000000000000000'), ).toBe('0x0000000000000000000000000000000000000000'); }); + + it('throws for an invalid hex address', () => { + expect(() => getChecksumAddress('0x')).toThrow('Invalid hex address.'); + }); }); describe('isValidChecksumAddress', () => { diff --git a/src/hex.ts b/src/hex.ts index 869f2d926..39bf32574 100644 --- a/src/hex.ts +++ b/src/hex.ts @@ -87,15 +87,14 @@ export function isValidHexAddress(possibleAddress: Hex) { * @see https://eips.ethereum.org/EIPS/eip-55 */ export function getChecksumAddress(address: Hex) { + assert(is(address, HexChecksumAddressStruct), 'Invalid hex address.'); const unPrefixed = remove0x(address.toLowerCase()); const unPrefixedHash = remove0x(bytesToHex(keccak256(unPrefixed))); return `0x${unPrefixed .split('') .map((character, nibbleIndex) => { const hashCharacter = unPrefixedHash[nibbleIndex]; - if (!hashCharacter) { - throw new Error('Hash shorter than address'); - } + assert(is(hashCharacter, string()), 'Hash shorter than address.'); return parseInt(hashCharacter, 16) > 7 ? character.toUpperCase() : character;