-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
26 changed files
with
3,669 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { keccak256 } from "js-sha3"; | ||
|
||
import { assertArgument } from "./errors"; | ||
import { getBytes } from "./utils/data"; | ||
|
||
const BN_0 = BigInt(0); | ||
const BN_36 = BigInt(36); | ||
|
||
function getChecksumAddress(address: string): string { | ||
address = address.toLowerCase(); | ||
|
||
const chars = address.substring(2).split(""); | ||
|
||
const expanded = new Uint8Array(40); | ||
for (let i = 0; i < 40; i++) { | ||
expanded[i] = chars[i].charCodeAt(0); | ||
} | ||
|
||
const hashed = getBytes(keccak256(expanded)); | ||
|
||
for (let i = 0; i < 40; i += 2) { | ||
if (hashed[i >> 1] >> 4 >= 8) { | ||
chars[i] = chars[i].toUpperCase(); | ||
} | ||
if ((hashed[i >> 1] & 0x0f) >= 8) { | ||
chars[i + 1] = chars[i + 1].toUpperCase(); | ||
} | ||
} | ||
|
||
return "0x" + chars.join(""); | ||
} | ||
|
||
// See: https://en.wikipedia.org/wiki/International_Bank_Account_Number | ||
|
||
// Create lookup table | ||
const ibanLookup: { [character: string]: string } = {}; | ||
for (let i = 0; i < 10; i++) { | ||
ibanLookup[String(i)] = String(i); | ||
} | ||
for (let i = 0; i < 26; i++) { | ||
ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); | ||
} | ||
|
||
// How many decimal digits can we process? (for 64-bit float, this is 15) | ||
// i.e. Math.floor(Math.log10(Number.MAX_SAFE_INTEGER)); | ||
const safeDigits = 15; | ||
|
||
function ibanChecksum(address: string): string { | ||
address = address.toUpperCase(); | ||
address = address.substring(4) + address.substring(0, 2) + "00"; | ||
|
||
let expanded = address | ||
.split("") | ||
.map((c) => { | ||
return ibanLookup[c]; | ||
}) | ||
.join(""); | ||
|
||
// Javascript can handle integers safely up to 15 (decimal) digits | ||
while (expanded.length >= safeDigits) { | ||
const block = expanded.substring(0, safeDigits); | ||
expanded = (parseInt(block, 10) % 97) + expanded.substring(block.length); | ||
} | ||
|
||
let checksum = String(98 - (parseInt(expanded, 10) % 97)); | ||
while (checksum.length < 2) { | ||
checksum = "0" + checksum; | ||
} | ||
|
||
return checksum; | ||
} | ||
|
||
const Base36 = (function () { | ||
const result: Record<string, bigint> = {}; | ||
for (let i = 0; i < 36; i++) { | ||
const key = "0123456789abcdefghijklmnopqrstuvwxyz"[i]; | ||
result[key] = BigInt(i); | ||
} | ||
return result; | ||
})(); | ||
|
||
function fromBase36(value: string): bigint { | ||
value = value.toLowerCase(); | ||
|
||
let result = BN_0; | ||
for (let i = 0; i < value.length; i++) { | ||
result = result * BN_36 + Base36[value[i]]; | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Returns a normalized and checksumed address for %%address%%. | ||
* This accepts non-checksum addresses, checksum addresses and | ||
* [[getIcapAddress]] formats. | ||
* | ||
* The checksum in Ethereum uses the capitalization (upper-case | ||
* vs lower-case) of the characters within an address to encode | ||
* its checksum, which offers, on average, a checksum of 15-bits. | ||
* | ||
* If %%address%% contains both upper-case and lower-case, it is | ||
* assumed to already be a checksum address and its checksum is | ||
* validated, and if the address fails its expected checksum an | ||
* error is thrown. | ||
* | ||
* If you wish the checksum of %%address%% to be ignore, it should | ||
* be converted to lower-case (i.e. ``.toLowercase()``) before | ||
* being passed in. This should be a very rare situation though, | ||
* that you wish to bypass the safegaurds in place to protect | ||
* against an address that has been incorrectly copied from another | ||
* source. | ||
* | ||
* @example: | ||
* // Adds the checksum (via upper-casing specific letters) | ||
* getAddress("0x8ba1f109551bd432803012645ac136ddd64dba72") | ||
* //_result: | ||
* | ||
* // Converts ICAP address and adds checksum | ||
* getAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36"); | ||
* //_result: | ||
* | ||
* // Throws an error if an address contains mixed case, | ||
* // but the checksum fails | ||
* getAddress("0x8Ba1f109551bD432803012645Ac136ddd64DBA72") | ||
* //_error: | ||
*/ | ||
export function getAddress(address: string): string { | ||
assertArgument(typeof address === "string", "invalid address", "address", address); | ||
|
||
if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) { | ||
// Missing the 0x prefix | ||
if (!address.startsWith("0x")) { | ||
address = "0x" + address; | ||
} | ||
|
||
const result = getChecksumAddress(address); | ||
|
||
// It is a checksummed address with a bad checksum | ||
assertArgument( | ||
!address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) || result === address, | ||
"bad address checksum", | ||
"address", | ||
address | ||
); | ||
|
||
return result; | ||
} | ||
|
||
// Maybe ICAP? (we only support direct mode) | ||
if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) { | ||
// It is an ICAP address with a bad checksum | ||
assertArgument( | ||
address.substring(2, 4) === ibanChecksum(address), | ||
"bad icap checksum", | ||
"address", | ||
address | ||
); | ||
|
||
let result = fromBase36(address.substring(4)).toString(16); | ||
while (result.length < 40) { | ||
result = "0" + result; | ||
} | ||
return getChecksumAddress("0x" + result); | ||
} | ||
|
||
assertArgument(false, "invalid address", "address", address); | ||
} | ||
|
||
/** | ||
* The [ICAP Address format](link-icap) format is an early checksum | ||
* format which attempts to be compatible with the banking | ||
* industry [IBAN format](link-wiki-iban) for bank accounts. | ||
* | ||
* It is no longer common or a recommended format. | ||
* | ||
* @example: | ||
* getIcapAddress("0x8ba1f109551bd432803012645ac136ddd64dba72"); | ||
* //_result: | ||
* | ||
* getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36"); | ||
* //_result: | ||
* | ||
* // Throws an error if the ICAP checksum is wrong | ||
* getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK37"); | ||
* //_error: | ||
*/ | ||
export function getIcapAddress(address: string): string { | ||
//let base36 = _base16To36(getAddress(address).substring(2)).toUpperCase(); | ||
let base36 = BigInt(getAddress(address)).toString(36).toUpperCase(); | ||
while (base36.length < 30) { | ||
base36 = "0" + base36; | ||
} | ||
return "XE" + ibanChecksum("XE00" + base36) + base36; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* A constant for the zero hash. | ||
* | ||
* (**i.e.** ``"0x0000000000000000000000000000000000000000000000000000000000000000"``) | ||
*/ | ||
export const ZeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/** | ||
* Cryptographic hashing functions | ||
* | ||
* @_subsection: api/crypto:Hash Functions [about-crypto-hashing] | ||
*/ | ||
|
||
/* | ||
I changed this to save us a bit of space rather than import another keccak lib | ||
import { keccak_256 } from "@noble/hashes/sha3"; | ||
*/ | ||
import { keccak256 as keccak256_js_sha3 } from "js-sha3"; | ||
|
||
import { BytesLike, getBytes, hexlify } from "../utils/data"; | ||
|
||
let locked = false; | ||
|
||
const _keccak256 = function (data: Uint8Array): Uint8Array { | ||
return new Uint8Array(keccak256_js_sha3.arrayBuffer(data)); | ||
}; | ||
|
||
let __keccak256: (data: Uint8Array) => BytesLike = _keccak256; | ||
|
||
/** | ||
* Compute the cryptographic KECCAK256 hash of %%data%%. | ||
* | ||
* The %%data%% **m | ||
* ust** be a data representation, to compute the | ||
* hash of UTF-8 data use the [[id]] function. | ||
* | ||
* @returns DataHexstring | ||
* @example: | ||
* keccak256("0x") | ||
* //_result: | ||
* | ||
* keccak256("0x1337") | ||
* //_result: | ||
* | ||
* keccak256(new Uint8Array([ 0x13, 0x37 ])) | ||
* //_result: | ||
* | ||
* // Strings are assumed to be DataHexString, otherwise it will | ||
* // throw. To hash UTF-8 data, see the note above. | ||
* keccak256("Hello World") | ||
* //_error: | ||
*/ | ||
export function keccak256(_data: BytesLike): string { | ||
const data = getBytes(_data, "data"); | ||
return hexlify(__keccak256(data)); | ||
} | ||
keccak256._ = _keccak256; | ||
keccak256.lock = function (): void { | ||
locked = true; | ||
}; | ||
keccak256.register = function (func: (data: Uint8Array) => BytesLike) { | ||
if (locked) { | ||
throw new TypeError("keccak256 is locked"); | ||
} | ||
__keccak256 = func; | ||
}; | ||
Object.freeze(keccak256); |
Oops, something went wrong.