Can specify domain separator, not only domain object.

This commit is contained in:
TechGeorgii 2024-02-16 16:47:01 +07:00
parent 3a1d175c20
commit 92f9ed1caf
2 changed files with 35 additions and 24 deletions

View File

@ -4,7 +4,8 @@ import { keccak256 } from "../crypto/index.js";
import { recoverAddress } from "../transaction/index.js"; import { recoverAddress } from "../transaction/index.js";
import { import {
concat, defineProperties, getBigInt, getBytes, hexlify, isHexString, mask, toBeHex, toQuantity, toTwos, zeroPadValue, concat, defineProperties, getBigInt, getBytes, hexlify, isHexString, mask, toBeHex, toQuantity, toTwos, zeroPadValue,
assertArgument assertArgument,
isBytesLike
} from "../utils/index.js"; } from "../utils/index.js";
import { id } from "./id.js"; import { id } from "./id.js";
@ -517,10 +518,11 @@ export class TypedDataEncoder {
/** /**
* Return the fully encoded [[link-eip-712]] %%value%% for %%types%% with %%domain%%. * Return the fully encoded [[link-eip-712]] %%value%% for %%types%% with %%domain%%.
*/ */
static encode(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string { static encode(domain: TypedDataDomain | BytesLike, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string {
return concat([ return concat([
"0x1901", "0x1901",
TypedDataEncoder.hashDomain(domain), // if domainSeparator is passed (array of bytes), then just use it.
isBytesLike(domain) ? domain : TypedDataEncoder.hashDomain(domain),
TypedDataEncoder.from(types).hash(value) TypedDataEncoder.from(types).hash(value)
]); ]);
} }
@ -528,7 +530,7 @@ export class TypedDataEncoder {
/** /**
* Return the hash of the fully encoded [[link-eip-712]] %%value%% for %%types%% with %%domain%%. * Return the hash of the fully encoded [[link-eip-712]] %%value%% for %%types%% with %%domain%%.
*/ */
static hash(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string { static hash(domain: TypedDataDomain | BytesLike, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): string {
return keccak256(TypedDataEncoder.encode(domain, types, value)); return keccak256(TypedDataEncoder.encode(domain, types, value));
} }
@ -537,23 +539,26 @@ export class TypedDataEncoder {
* Resolves to the value from resolving all addresses in %%value%% for * Resolves to the value from resolving all addresses in %%value%% for
* %%types%% and the %%domain%%. * %%types%% and the %%domain%%.
*/ */
static async resolveNames(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>, resolveName: (name: string) => Promise<string>): Promise<{ domain: TypedDataDomain, value: any }> { static async resolveNames<T extends TypedDataDomain | BytesLike>(domain: T, types: Record<string, Array<TypedDataField>>, value: Record<string, any>, resolveName: (name: string) => Promise<string>): Promise<{ domain: T, value: any }> {
// Make a copy to isolate it from the object passed in
domain = Object.assign({ }, domain);
// Allow passing null to ignore value
for (const key in domain) {
if ((<Record<string, any>>domain)[key] == null) {
delete (<Record<string, any>>domain)[key];
}
}
// Look up all ENS names // Look up all ENS names
const ensCache: Record<string, string> = { }; const ensCache: Record<string, string> = { };
// Do we need to look up the domain's verifyingContract? if (!isBytesLike(domain)) {
if (domain.verifyingContract && !isHexString(domain.verifyingContract, 20)) { // Make a copy to isolate it from the object passed in
ensCache[domain.verifyingContract] = "0x"; domain = Object.assign({}, domain);
}
if (!isBytesLike(domain)) {
// Allow passing null to ignore value
for (const key in domain) {
if ((<Record<string, any>>domain)[key] == null) {
delete (<Record<string, any>>domain)[key];
}
}
// Do we need to look up the domain's verifyingContract?
if (domain.verifyingContract && !isHexString(domain.verifyingContract, 20)) {
ensCache[domain.verifyingContract] = "0x";
}
} }
// We are going to use the encoder to visit all the base values // We are going to use the encoder to visit all the base values
@ -572,9 +577,11 @@ export class TypedDataEncoder {
ensCache[name] = await resolveName(name); ensCache[name] = await resolveName(name);
} }
// Replace the domain verifyingContract if needed if (!isBytesLike(domain)) {
if (domain.verifyingContract && ensCache[domain.verifyingContract]) { // Replace the domain verifyingContract if needed
domain.verifyingContract = ensCache[domain.verifyingContract]; if (domain.verifyingContract && ensCache[domain.verifyingContract]) {
domain.verifyingContract = ensCache[domain.verifyingContract];
}
} }
// Replace all ENS names with their address // Replace all ENS names with their address
@ -652,7 +659,8 @@ export class TypedDataEncoder {
/** /**
* Compute the address used to sign the typed data for the %%signature%%. * Compute the address used to sign the typed data for the %%signature%%.
* DOMAIN_SEPARATOR can be passed into %%domain%% directly.
*/ */
export function verifyTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>, signature: SignatureLike): string { export function verifyTypedData(domain: TypedDataDomain | BytesLike, types: Record<string, Array<TypedDataField>>, value: Record<string, any>, signature: SignatureLike): string {
return recoverAddress(TypedDataEncoder.hash(domain, types, value), signature); return recoverAddress(TypedDataEncoder.hash(domain, types, value), signature);
} }

View File

@ -3,7 +3,7 @@ import { hashMessage, TypedDataEncoder } from "../hash/index.js";
import { AbstractSigner } from "../providers/index.js"; import { AbstractSigner } from "../providers/index.js";
import { computeAddress, Transaction } from "../transaction/index.js"; import { computeAddress, Transaction } from "../transaction/index.js";
import { import {
defineProperties, resolveProperties, assert, assertArgument defineProperties, resolveProperties, assert, assertArgument, BytesLike
} from "../utils/index.js"; } from "../utils/index.js";
import type { SigningKey } from "../crypto/index.js"; import type { SigningKey } from "../crypto/index.js";
@ -105,7 +105,10 @@ export class BaseWallet extends AbstractSigner {
return this.signingKey.sign(hashMessage(message)).serialized; return this.signingKey.sign(hashMessage(message)).serialized;
} }
async signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> { /**
* DOMAIN_SEPARATOR can be passed into %%domain%% directly.
*/
async signTypedData(domain: TypedDataDomain | BytesLike, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
// Populate any ENS names // Populate any ENS names
const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => { const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => {