diff --git a/packages/ethers/src.ts/utils.ts b/packages/ethers/src.ts/utils.ts index 3e1c4849b3..508457c6a4 100644 --- a/packages/ethers/src.ts/utils.ts +++ b/packages/ethers/src.ts/utils.ts @@ -5,7 +5,7 @@ import { getAddress, getCreate2Address, getContractAddress, getIcapAddress, isAd import * as base64 from "@ethersproject/base64"; import { Base58 as base58 } from "@ethersproject/basex"; import { arrayify, concat, hexConcat, hexDataSlice, hexDataLength, hexlify, hexStripZeros, hexValue, hexZeroPad, isBytes, isBytesLike, isHexString, joinSignature, zeroPad, splitSignature, stripZeros } from "@ethersproject/bytes"; -import { hashMessage, id, isValidName, namehash } from "@ethersproject/hash"; +import { _TypedDataEncoder, hashMessage, id, isValidName, namehash } from "@ethersproject/hash"; import { defaultPath, entropyToMnemonic, HDNode, isValidMnemonic, mnemonicToEntropy, mnemonicToSeed } from "@ethersproject/hdnode"; import { getJsonWalletAddress } from "@ethersproject/json-wallets"; import { keccak256 } from "@ethersproject/keccak256"; @@ -116,6 +116,8 @@ export { isValidName, id, + _TypedDataEncoder, + getAddress, getIcapAddress, getContractAddress, diff --git a/packages/hash/src.ts/id.ts b/packages/hash/src.ts/id.ts new file mode 100644 index 0000000000..13c71d5c04 --- /dev/null +++ b/packages/hash/src.ts/id.ts @@ -0,0 +1,6 @@ +import { keccak256 } from "@ethersproject/keccak256"; +import { toUtf8Bytes } from "@ethersproject/strings"; + +export function id(text: string): string { + return keccak256(toUtf8Bytes(text)); +} diff --git a/packages/hash/src.ts/index.ts b/packages/hash/src.ts/index.ts index 8f147dba2e..464cde2e13 100644 --- a/packages/hash/src.ts/index.ts +++ b/packages/hash/src.ts/index.ts @@ -8,20 +8,14 @@ import { Logger } from "@ethersproject/logger"; import { version } from "./_version"; const logger = new Logger(version); -import { - getPrimaryType as _getPrimaryType, - hashStruct as _hashStruct, - hashTypedData as _hashTypedData, - hashTypedDataDomain as _hashTypedDataDomain, - TypedDataEncoder as _TypedDataEncoder -} from "./typed-data"; +import { TypedDataEncoder as _TypedDataEncoder } from "./typed-data"; + +import { id } from "./id"; export { - _getPrimaryType, - _hashStruct, - _hashTypedData, - _hashTypedDataDomain, - _TypedDataEncoder + _TypedDataEncoder, + + id } /////////////////////////////// @@ -61,10 +55,6 @@ export function namehash(name: string): string { } -export function id(text: string): string { - return keccak256(toUtf8Bytes(text)); -} - export const messagePrefix = "\x19Ethereum Signed Message:\n"; export function hashMessage(message: Bytes | string): string { diff --git a/packages/hash/src.ts/typed-data.ts b/packages/hash/src.ts/typed-data.ts index 86c04abf2a..f1cad53807 100644 --- a/packages/hash/src.ts/typed-data.ts +++ b/packages/hash/src.ts/typed-data.ts @@ -9,7 +9,7 @@ import { Logger } from "@ethersproject/logger"; import { version } from "./_version"; const logger = new Logger(version); -import { id } from "./index"; +import { id } from "./id"; const padding = new Uint8Array(32); padding.fill(0); @@ -109,11 +109,13 @@ export class TypedDataEncoder { readonly primaryType: string; readonly types: Record>; + readonly _encoderCache: Record string>; readonly _types: Record; constructor(types: Record>) { defineReadOnly(this, "types", Object.freeze(deepCopy(types))); + defineReadOnly(this, "_encoderCache", { }); defineReadOnly(this, "_types", { }); // Link struct types to their direct child structs @@ -206,6 +208,14 @@ export class TypedDataEncoder { } } + getEncoder(type: string): (value: any) => string { + let encoder = this._encoderCache[type]; + if (!encoder) { + encoder = this._encoderCache[type] = this._getEncoder(type); + } + return encoder; + } + _getEncoder(type: string): (value: any) => string { const match = type.match(/^([^\x5b]*)(\x5b(\d*)\x5d)?$/); if (!match) { logger.throwArgumentError(`unknown type: ${ type }`, "type", type); } @@ -222,7 +232,7 @@ export class TypedDataEncoder { const encodedType = id(this._types[baseType]); baseEncoder = (value: Record) => { const values = fields.map((f) => { - const result = this._getEncoder(f.type)(value[f.name]); + const result = this.getEncoder(f.type)(value[f.name]); if (this._types[f.type]) { return keccak256(result); } return result; }); @@ -259,7 +269,7 @@ export class TypedDataEncoder { } encodeData(type: string, value: any): string { - return this._getEncoder(type)(value); + return this.getEncoder(type)(value); } hashStruct(name: string, value: Record): string { @@ -273,32 +283,37 @@ export class TypedDataEncoder { hash(value: Record): string { return this.hashStruct(this.primaryType, value); } -} -export function getPrimaryType(types: Record>): string { - return (new TypedDataEncoder(types)).primaryType; -} + static from(types: Record>): TypedDataEncoder { + return new TypedDataEncoder(types); + } -export function hashStruct(name: string, types: Record>, value: Record): string { - return (new TypedDataEncoder(types)).hashStruct(name, value); -} + static getPrimaryType(types: Record>): string { + return TypedDataEncoder.from(types).primaryType; + } + + static hashStruct(name: string, types: Record>, value: Record): string { + return TypedDataEncoder.from(types).hashStruct(name, value); + } -export function hashTypedDataDomain(domain: TypedDataDomain): string { - const domainFields: Array = [ ]; - for (const name in domain) { - const type = domainFieldTypes[name]; - if (!type) { - logger.throwArgumentError(`invalid typed-data domain key: ${ JSON.stringify(name) }`, "domain", domain); + static hashTypedDataDomain(domain: TypedDataDomain): string { + const domainFields: Array = [ ]; + for (const name in domain) { + const type = domainFieldTypes[name]; + if (!type) { + logger.throwArgumentError(`invalid typed-data domain key: ${ JSON.stringify(name) }`, "domain", domain); + } + domainFields.push({ name, type }); } - domainFields.push({ name, type }); + return TypedDataEncoder.hashStruct("EIP712Domain", { EIP712Domain: domainFields }, domain); } - return hashStruct("EIP712Domain", { EIP712Domain: domainFields }, domain); -} -export function hashTypedData(domain: TypedDataDomain, types: Record>, value: Record): string { - return keccak256(concat([ - "0x1901", - hashTypedDataDomain(domain), - (new TypedDataEncoder(types)).hash(value) - ])); + static hashTypedData(domain: TypedDataDomain, types: Record>, value: Record): string { + return keccak256(concat([ + "0x1901", + TypedDataEncoder.hashTypedDataDomain(domain), + TypedDataEncoder.from(types).hash(value) + ])); + } } +