Skip to content

Commit

Permalink
Consolidated TypedDataEncoder methods (#687).
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed Oct 10, 2020
1 parent cfa6dec commit 345a830
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 42 deletions.
4 changes: 3 additions & 1 deletion packages/ethers/src.ts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -116,6 +116,8 @@ export {
isValidName,
id,

_TypedDataEncoder,

getAddress,
getIcapAddress,
getContractAddress,
Expand Down
6 changes: 6 additions & 0 deletions packages/hash/src.ts/id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { keccak256 } from "@ethersproject/keccak256";
import { toUtf8Bytes } from "@ethersproject/strings";

export function id(text: string): string {
return keccak256(toUtf8Bytes(text));
}
22 changes: 6 additions & 16 deletions packages/hash/src.ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

///////////////////////////////
Expand Down Expand Up @@ -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 {
Expand Down
65 changes: 40 additions & 25 deletions packages/hash/src.ts/typed-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -109,11 +109,13 @@ export class TypedDataEncoder {
readonly primaryType: string;
readonly types: Record<string, Array<TypedDataField>>;

readonly _encoderCache: Record<string, (value: any) => string>;
readonly _types: Record<string, string>;

constructor(types: Record<string, Array<TypedDataField>>) {
defineReadOnly(this, "types", Object.freeze(deepCopy(types)));

defineReadOnly(this, "_encoderCache", { });
defineReadOnly(this, "_types", { });

// Link struct types to their direct child structs
Expand Down Expand Up @@ -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); }
Expand All @@ -222,7 +232,7 @@ export class TypedDataEncoder {
const encodedType = id(this._types[baseType]);
baseEncoder = (value: Record<string, any>) => {
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;
});
Expand Down Expand Up @@ -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, any>): string {
Expand All @@ -273,32 +283,37 @@ export class TypedDataEncoder {
hash(value: Record<string, any>): string {
return this.hashStruct(this.primaryType, value);
}
}

export function getPrimaryType(types: Record<string, Array<TypedDataField>>): string {
return (new TypedDataEncoder(types)).primaryType;
}
static from(types: Record<string, Array<TypedDataField>>): TypedDataEncoder {
return new TypedDataEncoder(types);
}

export function hashStruct(name: string, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string {
return (new TypedDataEncoder(types)).hashStruct(name, value);
}
static getPrimaryType(types: Record<string, Array<TypedDataField>>): string {
return TypedDataEncoder.from(types).primaryType;
}

static hashStruct(name: string, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string {
return TypedDataEncoder.from(types).hashStruct(name, value);
}

export function hashTypedDataDomain(domain: TypedDataDomain): string {
const domainFields: Array<TypedDataField> = [ ];
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<TypedDataField> = [ ];
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<string, Array<TypedDataField>>, value: Record<string, any>): string {
return keccak256(concat([
"0x1901",
hashTypedDataDomain(domain),
(new TypedDataEncoder(types)).hash(value)
]));
static hashTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string {
return keccak256(concat([
"0x1901",
TypedDataEncoder.hashTypedDataDomain(domain),
TypedDataEncoder.from(types).hash(value)
]));
}
}

0 comments on commit 345a830

Please sign in to comment.