From d939de2bccd2cbc01455ba852a85bfdd40c4360a Mon Sep 17 00:00:00 2001 From: horsefacts Date: Tue, 21 Nov 2023 13:22:51 -0500 Subject: [PATCH] feat: add EIP-712 helpers --- packages/core/src/eth/contracts/idGateway.ts | 46 ++++ packages/core/src/eth/contracts/idRegistry.ts | 118 +++++++++ packages/core/src/eth/contracts/keyGateway.ts | 52 ++++ .../core/src/eth/contracts/keyRegistry.ts | 46 ++++ .../contracts/signedKeyRequestValidator.ts | 44 ++++ packages/core/src/signers/eip712Signer.ts | 20 ++ .../core/src/signers/ethersEip712Signer.ts | 99 ++++++++ .../core/src/signers/ethersV5Eip712Signer.ts | 107 ++++++++ packages/core/src/signers/testUtils.ts | 236 ++++++++++++++++++ .../core/src/signers/viemLocalEip712Signer.ts | 118 +++++++++ 10 files changed, 886 insertions(+) create mode 100644 packages/core/src/eth/contracts/idGateway.ts create mode 100644 packages/core/src/eth/contracts/idRegistry.ts create mode 100644 packages/core/src/eth/contracts/keyGateway.ts create mode 100644 packages/core/src/eth/contracts/keyRegistry.ts create mode 100644 packages/core/src/eth/contracts/signedKeyRequestValidator.ts diff --git a/packages/core/src/eth/contracts/idGateway.ts b/packages/core/src/eth/contracts/idGateway.ts new file mode 100644 index 0000000000..19eb488661 --- /dev/null +++ b/packages/core/src/eth/contracts/idGateway.ts @@ -0,0 +1,46 @@ +import { HubAsyncResult, HubError } from "../../errors"; +import { ResultAsync } from "neverthrow"; +import { verifyTypedData, bytesToHex } from "viem"; + +export type IdGatewayRegisterMessage = { + to: `0x${string}`; + recovery: `0x${string}`; + nonce: bigint; + deadline: bigint; +}; + +export const ID_GATEWAY_ADDRESS = "0x00000000Fc25870C6eD6b6c7E41Fb078b7656f69" as const; + +export const ID_GATEWAY_EIP_712_DOMAIN = { + name: "Farcaster IdGateway", + version: "1", + chainId: 10, + verifyingContract: ID_GATEWAY_ADDRESS, +} as const; + +export const ID_GATEWAY_REGISTER_TYPE = [ + { name: "to", type: "address" }, + { name: "recovery", type: "address" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const verifyRegister = async ( + message: IdGatewayRegisterMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: ID_GATEWAY_EIP_712_DOMAIN, + types: { Register: ID_GATEWAY_REGISTER_TYPE }, + primaryType: "Register", + message, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; diff --git a/packages/core/src/eth/contracts/idRegistry.ts b/packages/core/src/eth/contracts/idRegistry.ts new file mode 100644 index 0000000000..21d542d31a --- /dev/null +++ b/packages/core/src/eth/contracts/idRegistry.ts @@ -0,0 +1,118 @@ +import { HubAsyncResult, HubError } from "../../errors"; +import { ResultAsync } from "neverthrow"; +import { verifyTypedData, bytesToHex } from "viem"; + +export type IdRegistryTransferMessage = { + fid: bigint; + to: `0x${string}`; + nonce: bigint; + deadline: bigint; +}; + +export type IdRegistryTransferAndChangeRecoveryMessage = { + fid: bigint; + to: `0x${string}`; + recovery: `0x${string}`; + nonce: bigint; + deadline: bigint; +}; + +export type IdRegistryChangeRecoveryAddressMessage = { + fid: bigint; + from: `0x${string}`; + to: `0x${string}`; + nonce: bigint; + deadline: bigint; +}; + +export const ID_REGISTRY_ADDRESS = "0x00000000Fc6c5F01Fc30151999387Bb99A9f489b" as const; + +export const ID_REGISTRY_EIP_712_DOMAIN = { + name: "Farcaster IdRegistry", + version: "1", + chainId: 10, + verifyingContract: ID_REGISTRY_ADDRESS, +} as const; + +export const ID_REGISTRY_TRANSFER_TYPE = [ + { name: "fid", type: "uint256" }, + { name: "to", type: "address" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE = [ + { name: "fid", type: "uint256" }, + { name: "to", type: "address" }, + { name: "recovery", type: "address" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE = [ + { name: "fid", type: "uint256" }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const verifyTransfer = async ( + message: IdRegistryTransferMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: ID_REGISTRY_EIP_712_DOMAIN, + types: { Transfer: ID_REGISTRY_TRANSFER_TYPE }, + primaryType: "Transfer", + message, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; + +export const verifyTransferAndChangeRecovery = async ( + message: IdRegistryTransferAndChangeRecoveryMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: ID_REGISTRY_EIP_712_DOMAIN, + types: { TransferAndChangeRecovery: ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE }, + primaryType: "TransferAndChangeRecovery", + message, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; + +export const verifyChangeRecoveryAddress = async ( + message: IdRegistryChangeRecoveryAddressMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: ID_REGISTRY_EIP_712_DOMAIN, + types: { ChangeRecoveryAddress: ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE }, + primaryType: "ChangeRecoveryAddress", + message, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; diff --git a/packages/core/src/eth/contracts/keyGateway.ts b/packages/core/src/eth/contracts/keyGateway.ts new file mode 100644 index 0000000000..f4f870e6a5 --- /dev/null +++ b/packages/core/src/eth/contracts/keyGateway.ts @@ -0,0 +1,52 @@ +import { HubAsyncResult, HubError } from "../../errors"; +import { ResultAsync } from "neverthrow"; +import { verifyTypedData, bytesToHex } from "viem"; + +export type KeyGatewayAddMessage = { + owner: `0x${string}`; + keyType: number; + key: Uint8Array; + metadataType: number; + metadata: `0x${string}`; + nonce: bigint; + deadline: bigint; +}; + +export const KEY_GATEWAY_ADDRESS = "0x00000000fC56947c7E7183f8Ca4B62398CaAdf0B" as const; + +export const KEY_GATEWAY_EIP_712_DOMAIN = { + name: "Farcaster KeyGateway", + version: "1", + chainId: 10, + verifyingContract: KEY_GATEWAY_ADDRESS, +} as const; + +export const KEY_GATEWAY_ADD_TYPE = [ + { name: "owner", type: "address" }, + { name: "keyType", type: "uint32" }, + { name: "key", type: "bytes" }, + { name: "metadataType", type: "uint8" }, + { name: "metadata", type: "bytes" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const verifyAdd = async ( + message: KeyGatewayAddMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: KEY_GATEWAY_EIP_712_DOMAIN, + types: { Add: KEY_GATEWAY_ADD_TYPE }, + primaryType: "Add", + message: { ...message, key: bytesToHex(message.key) }, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; diff --git a/packages/core/src/eth/contracts/keyRegistry.ts b/packages/core/src/eth/contracts/keyRegistry.ts new file mode 100644 index 0000000000..1ca3016a50 --- /dev/null +++ b/packages/core/src/eth/contracts/keyRegistry.ts @@ -0,0 +1,46 @@ +import { HubAsyncResult, HubError } from "../../errors"; +import { ResultAsync } from "neverthrow"; +import { verifyTypedData, bytesToHex } from "viem"; + +export type KeyRegistryRemoveMessage = { + owner: `0x${string}`; + key: Uint8Array; + nonce: bigint; + deadline: bigint; +}; + +export const KEY_REGISTRY_ADDRESS = "0x00000000fc1237824fb747abde0ff18990e59b7e" as const; + +export const KEY_REGISTRY_EIP_712_DOMAIN = { + name: "Farcaster KeyRegistry", + version: "1", + chainId: 10, + verifyingContract: KEY_REGISTRY_ADDRESS, +} as const; + +export const KEY_REGISTRY_REMOVE_TYPE = [ + { name: "owner", type: "address" }, + { name: "key", type: "bytes" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const verifyRemove = async ( + message: KeyRegistryRemoveMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: KEY_REGISTRY_EIP_712_DOMAIN, + types: { Remove: KEY_REGISTRY_REMOVE_TYPE }, + primaryType: "Remove", + message: { ...message, key: bytesToHex(message.key) }, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; diff --git a/packages/core/src/eth/contracts/signedKeyRequestValidator.ts b/packages/core/src/eth/contracts/signedKeyRequestValidator.ts new file mode 100644 index 0000000000..c1620e1b5b --- /dev/null +++ b/packages/core/src/eth/contracts/signedKeyRequestValidator.ts @@ -0,0 +1,44 @@ +import { HubAsyncResult, HubError } from "../../errors"; +import { ResultAsync } from "neverthrow"; +import { verifyTypedData, bytesToHex } from "viem"; + +export type SignedKeyRequestMessage = { + requestFid: bigint; + key: Uint8Array; + deadline: bigint; +}; + +export const SIGNED_KEY_REQUEST_VALIDATOR_ADDRESS = "0x00000000FC700472606ED4fA22623Acf62c60553" as const; + +export const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = { + name: "Farcaster SignedKeyRequestValidator", + version: "1", + chainId: 10, + verifyingContract: SIGNED_KEY_REQUEST_VALIDATOR_ADDRESS, +} as const; + +export const SIGNED_KEY_REQUEST_TYPE = [ + { name: "requestFid", type: "uint256" }, + { name: "key", type: "bytes" }, + { name: "deadline", type: "uint256" }, +] as const; + +export const verifyKeyRequest = async ( + message: SignedKeyRequestMessage, + signature: Uint8Array, + address: Uint8Array, +): HubAsyncResult => { + const valid = await ResultAsync.fromPromise( + verifyTypedData({ + address: bytesToHex(address), + domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + types: { SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE }, + primaryType: "SignedKeyRequest", + message: { ...message, key: bytesToHex(message.key) }, + signature, + }), + (e) => new HubError("unknown", e as Error), + ); + + return valid; +}; diff --git a/packages/core/src/signers/eip712Signer.ts b/packages/core/src/signers/eip712Signer.ts index 6b31fb5b3b..bc89983d47 100644 --- a/packages/core/src/signers/eip712Signer.ts +++ b/packages/core/src/signers/eip712Signer.ts @@ -3,6 +3,15 @@ import { HubAsyncResult } from "../errors"; import { VerificationEthAddressClaim } from "../verifications"; import { UserNameProofClaim } from "../userNameProof"; import { Signer } from "./signer"; +import { KeyGatewayAddMessage } from "../eth/contracts/keyGateway"; +import { KeyRegistryRemoveMessage } from "../eth/contracts/keyRegistry"; +import { IdGatewayRegisterMessage } from "../eth/contracts/idGateway"; +import { + IdRegistryChangeRecoveryAddressMessage, + IdRegistryTransferAndChangeRecoveryMessage, + IdRegistryTransferMessage, +} from "../eth/contracts/idRegistry"; +import { SignedKeyRequestMessage } from "../eth/contracts/signedKeyRequestValidator"; /** * Extend this class to implement an EIP712 signer. @@ -21,4 +30,15 @@ export abstract class Eip712Signer implements Signer { chainId?: number, ): HubAsyncResult; public abstract signUserNameProofClaim(claim: UserNameProofClaim): HubAsyncResult; + public abstract signRegister(message: IdGatewayRegisterMessage): HubAsyncResult; + public abstract signAdd(message: KeyGatewayAddMessage): HubAsyncResult; + public abstract signRemove(message: KeyRegistryRemoveMessage): HubAsyncResult; + public abstract signTransfer(message: IdRegistryTransferMessage): HubAsyncResult; + public abstract signTransferAndChangeRecovery( + message: IdRegistryTransferAndChangeRecoveryMessage, + ): HubAsyncResult; + public abstract signChangeRecoveryAddress( + message: IdRegistryChangeRecoveryAddressMessage, + ): HubAsyncResult; + public abstract signKeyRequest(message: SignedKeyRequestMessage): HubAsyncResult; } diff --git a/packages/core/src/signers/ethersEip712Signer.ts b/packages/core/src/signers/ethersEip712Signer.ts index bc91c4db87..561f289bef 100644 --- a/packages/core/src/signers/ethersEip712Signer.ts +++ b/packages/core/src/signers/ethersEip712Signer.ts @@ -12,6 +12,31 @@ import { EIP_712_USERNAME_DOMAIN, EIP_712_USERNAME_PROOF, } from "../crypto/eip712"; +import { + ID_GATEWAY_EIP_712_DOMAIN, + ID_GATEWAY_REGISTER_TYPE, + IdGatewayRegisterMessage, +} from "../eth/contracts/idGateway"; +import { + KeyRegistryRemoveMessage, + KEY_REGISTRY_EIP_712_DOMAIN, + KEY_REGISTRY_REMOVE_TYPE, +} from "../eth/contracts/keyRegistry"; +import { + IdRegistryTransferMessage, + ID_REGISTRY_EIP_712_DOMAIN, + ID_REGISTRY_TRANSFER_TYPE, + ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE, + IdRegistryTransferAndChangeRecoveryMessage, + ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE, + IdRegistryChangeRecoveryAddressMessage, +} from "../eth/contracts/idRegistry"; +import { KeyGatewayAddMessage, KEY_GATEWAY_EIP_712_DOMAIN, KEY_GATEWAY_ADD_TYPE } from "../eth/contracts/keyGateway"; +import { + SIGNED_KEY_REQUEST_TYPE, + SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + SignedKeyRequestMessage, +} from "../eth/contracts/signedKeyRequestValidator"; export type MinimalEthersSigner = Pick; @@ -70,4 +95,78 @@ export class EthersEip712Signer extends Eip712Signer { // Convert hex signature to bytes return hexSignature.andThen((hex) => hexStringToBytes(hex)); } + + public async signRegister(message: IdGatewayRegisterMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData(ID_GATEWAY_EIP_712_DOMAIN, { Register: [...ID_GATEWAY_REGISTER_TYPE] }, message), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signTransfer(message: IdRegistryTransferMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData( + ID_REGISTRY_EIP_712_DOMAIN, + { Transfer: [...ID_REGISTRY_TRANSFER_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signTransferAndChangeRecovery( + message: IdRegistryTransferAndChangeRecoveryMessage, + ): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData( + ID_REGISTRY_EIP_712_DOMAIN, + { TransferAndChangeRecovery: [...ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signChangeRecoveryAddress(message: IdRegistryChangeRecoveryAddressMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData( + ID_REGISTRY_EIP_712_DOMAIN, + { ChangeRecoveryAddress: [...ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signAdd(message: KeyGatewayAddMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData(KEY_GATEWAY_EIP_712_DOMAIN, { Add: [...KEY_GATEWAY_ADD_TYPE] }, message), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signRemove(message: KeyRegistryRemoveMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData(KEY_REGISTRY_EIP_712_DOMAIN, { Remove: [...KEY_REGISTRY_REMOVE_TYPE] }, message), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signKeyRequest(message: SignedKeyRequestMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._ethersSigner.signTypedData( + SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + { SignedKeyRequest: [...SIGNED_KEY_REQUEST_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } } diff --git a/packages/core/src/signers/ethersV5Eip712Signer.ts b/packages/core/src/signers/ethersV5Eip712Signer.ts index 8eba34ca92..f24fae555a 100644 --- a/packages/core/src/signers/ethersV5Eip712Signer.ts +++ b/packages/core/src/signers/ethersV5Eip712Signer.ts @@ -9,6 +9,31 @@ import { eip712 } from "../crypto"; import { hexStringToBytes } from "../bytes"; import { VerificationEthAddressClaim } from "../verifications"; import { UserNameProofClaim } from "../userNameProof"; +import { + ID_GATEWAY_EIP_712_DOMAIN, + ID_GATEWAY_REGISTER_TYPE, + IdGatewayRegisterMessage, +} from "../eth/contracts/idGateway"; +import { + KeyRegistryRemoveMessage, + KEY_REGISTRY_EIP_712_DOMAIN, + KEY_REGISTRY_REMOVE_TYPE, +} from "../eth/contracts/keyRegistry"; +import { + ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE, + ID_REGISTRY_EIP_712_DOMAIN, + ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE, + ID_REGISTRY_TRANSFER_TYPE, + IdRegistryChangeRecoveryAddressMessage, + IdRegistryTransferAndChangeRecoveryMessage, + IdRegistryTransferMessage, +} from "../eth/contracts/idRegistry"; +import { KeyGatewayAddMessage, KEY_GATEWAY_EIP_712_DOMAIN, KEY_GATEWAY_ADD_TYPE } from "../eth/contracts/keyGateway"; +import { + SIGNED_KEY_REQUEST_TYPE, + SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + SignedKeyRequestMessage, +} from "../eth/contracts/signedKeyRequestValidator"; export type TypedDataSigner = EthersAbstractSigner & EthersTypedDataSigner; @@ -66,4 +91,86 @@ export class EthersV5Eip712Signer extends Eip712Signer { ); return hexSignature.andThen((hex) => hexStringToBytes(hex)); } + + public async signRegister(message: IdGatewayRegisterMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData( + ID_GATEWAY_EIP_712_DOMAIN, + { Register: [...ID_GATEWAY_REGISTER_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signTransfer(message: IdRegistryTransferMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData( + ID_REGISTRY_EIP_712_DOMAIN, + { Transfer: [...ID_REGISTRY_TRANSFER_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signTransferAndChangeRecovery( + message: IdRegistryTransferAndChangeRecoveryMessage, + ): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData( + ID_REGISTRY_EIP_712_DOMAIN, + { TransferAndChangeRecovery: [...ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signChangeRecoveryAddress(message: IdRegistryChangeRecoveryAddressMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData( + ID_REGISTRY_EIP_712_DOMAIN, + { ChangeRecoveryAddress: [...ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signAdd(message: KeyGatewayAddMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData(KEY_GATEWAY_EIP_712_DOMAIN, { Add: [...KEY_GATEWAY_ADD_TYPE] }, message), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signRemove(message: KeyRegistryRemoveMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData( + KEY_REGISTRY_EIP_712_DOMAIN, + { Remove: [...KEY_REGISTRY_REMOVE_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signKeyRequest(message: SignedKeyRequestMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._typedDataSigner._signTypedData( + SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + { SignedKeyRequest: [...SIGNED_KEY_REQUEST_TYPE] }, + message, + ), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } } diff --git a/packages/core/src/signers/testUtils.ts b/packages/core/src/signers/testUtils.ts index 2a5957c467..5d2235e291 100644 --- a/packages/core/src/signers/testUtils.ts +++ b/packages/core/src/signers/testUtils.ts @@ -8,6 +8,17 @@ import { makeVerificationEthAddressClaim, VerificationEthAddressClaim } from ".. import { makeUserNameProofClaim, UserNameProofClaim } from "../userNameProof"; import { Eip712Signer } from "./eip712Signer"; import { bytesToHex } from "viem"; +import { IdGatewayRegisterMessage, verifyRegister } from "../eth/contracts/idGateway"; +import { KeyRegistryRemoveMessage, verifyRemove } from "../eth/contracts/keyRegistry"; +import { + IdRegistryChangeRecoveryAddressMessage, + IdRegistryTransferMessage, + verifyChangeRecoveryAddress, + verifyTransfer, + verifyTransferAndChangeRecovery, +} from "../eth/contracts/idRegistry"; +import { KeyGatewayAddMessage, verifyAdd } from "../eth/contracts/keyGateway"; +import { SignedKeyRequestMessage, verifyKeyRequest } from "../eth/contracts/signedKeyRequestValidator"; export const testEip712Signer = async (signer: Eip712Signer) => { let signerKey: Uint8Array; @@ -101,4 +112,229 @@ export const testEip712Signer = async (signer: Eip712Signer) => { expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); }); }); + + describe("signRegister", () => { + let message: IdGatewayRegisterMessage; + let signature: Uint8Array; + + beforeAll(async () => { + message = { + to: bytesToHex(signerKey), + recovery: bytesToHex(signerKey), + nonce: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signRegister(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyRegister(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signRegister({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); + + describe("signTransfer", () => { + let message: IdRegistryTransferMessage; + let signature: Uint8Array; + + beforeAll(async () => { + message = { + fid: 1n, + to: bytesToHex(signerKey), + nonce: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signTransfer(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyTransfer(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signTransfer({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); + + describe("signTransferAndChangeRecovery", () => { + let message: IdRegistryTransferAndChangeRecoveryMessage; + let signature: Uint8Array; + + beforeAll(async () => { + message = { + fid: 1n, + to: bytesToHex(signerKey), + recovery: bytesToHex(signerKey), + nonce: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signTransferAndChangeRecovery(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyTransferAndChangeRecovery(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signTransferAndChangeRecovery({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); + + describe("signChangeRecoveryAddress", () => { + let message: IdRegistryChangeRecoveryAddressMessage; + let signature: Uint8Array; + + beforeAll(async () => { + message = { + fid: 1n, + from: bytesToHex(signerKey), + to: bytesToHex(signerKey), + nonce: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signChangeRecoveryAddress(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyChangeRecoveryAddress(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signChangeRecoveryAddress({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); + + describe("signAdd", () => { + let message: KeyGatewayAddMessage; + let signature: Uint8Array; + + beforeAll(async () => { + const key = Factories.Bytes.build({}, { transient: { length: 65 } }); + const metadata = Factories.Bytes.build({}, { transient: { length: 65 } }); + message = { + owner: bytesToHex(signerKey), + keyType: 1, + key, + metadataType: 1, + metadata: bytesToHex(metadata), + nonce: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signAdd(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyAdd(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signAdd({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); + + describe("signRemove", () => { + let message: KeyRegistryRemoveMessage; + let signature: Uint8Array; + + beforeAll(async () => { + const key = Factories.Bytes.build({}, { transient: { length: 65 } }); + message = { + owner: bytesToHex(signerKey), + key, + nonce: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signRemove(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyRemove(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signRemove({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); + + describe("signKeyRequestMetadata", () => { + let message: SignedKeyRequestMessage; + let signature: Uint8Array; + + beforeAll(async () => { + const key = Factories.Bytes.build({}, { transient: { length: 65 } }); + message = { + requestFid: 1n, + key, + deadline: BigInt(Math.floor(Date.now() / 1000)), + }; + const signatureResult = await signer.signKeyRequest(message); + expect(signatureResult.isOk()).toBeTruthy(); + signature = signatureResult._unsafeUnwrap(); + }); + + test("succeeds", async () => { + const valid = await verifyKeyRequest(message, signature, signerKey); + expect(valid).toEqual(ok(true)); + }); + + test("fails with HubError", async () => { + const result = await signer.signKeyRequest({ + ...message, + deadline: -1n, + }); + expect(result.isErr()).toBe(true); + expect(result._unsafeUnwrapErr().errCode).toBe("bad_request.invalid_param"); + }); + }); }; diff --git a/packages/core/src/signers/viemLocalEip712Signer.ts b/packages/core/src/signers/viemLocalEip712Signer.ts index fc689b37b2..b2bf5cc8d8 100644 --- a/packages/core/src/signers/viemLocalEip712Signer.ts +++ b/packages/core/src/signers/viemLocalEip712Signer.ts @@ -13,6 +13,31 @@ import { HubAsyncResult, HubError } from "../errors"; import { VerificationEthAddressClaim } from "../verifications"; import { UserNameProofClaim } from "../userNameProof"; import { Eip712Signer } from "./eip712Signer"; +import { + ID_GATEWAY_EIP_712_DOMAIN, + ID_GATEWAY_REGISTER_TYPE, + IdGatewayRegisterMessage, +} from "../eth/contracts/idGateway"; +import { + KeyRegistryRemoveMessage, + KEY_REGISTRY_EIP_712_DOMAIN, + KEY_REGISTRY_REMOVE_TYPE, +} from "../eth/contracts/keyRegistry"; +import { + IdRegistryTransferMessage, + ID_REGISTRY_EIP_712_DOMAIN, + ID_REGISTRY_TRANSFER_TYPE, + ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE, + IdRegistryTransferAndChangeRecoveryMessage, + ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE, + IdRegistryChangeRecoveryAddressMessage, +} from "../eth/contracts/idRegistry"; +import { KEY_GATEWAY_ADD_TYPE, KEY_GATEWAY_EIP_712_DOMAIN, KeyGatewayAddMessage } from "../eth/contracts/keyGateway"; +import { + SIGNED_KEY_REQUEST_TYPE, + SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + SignedKeyRequestMessage, +} from "../eth/contracts/signedKeyRequestValidator"; export class ViemLocalEip712Signer extends Eip712Signer { private readonly _viemLocalAccount: LocalAccount; @@ -73,4 +98,97 @@ export class ViemLocalEip712Signer extends Eip712Signer { ); return hexSignature.andThen((hex) => hexStringToBytes(hex)); } + + public async signRegister(message: IdGatewayRegisterMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: ID_GATEWAY_EIP_712_DOMAIN, + types: { Register: ID_GATEWAY_REGISTER_TYPE }, + primaryType: "Register", + message, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signTransfer(message: IdRegistryTransferMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: ID_REGISTRY_EIP_712_DOMAIN, + types: { Transfer: ID_REGISTRY_TRANSFER_TYPE }, + primaryType: "Transfer", + message, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signTransferAndChangeRecovery( + message: IdRegistryTransferAndChangeRecoveryMessage, + ): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: ID_REGISTRY_EIP_712_DOMAIN, + types: { TransferAndChangeRecovery: ID_REGISTRY_TRANSFER_AND_CHANGE_RECOVERY_TYPE }, + primaryType: "TransferAndChangeRecovery", + message, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signChangeRecoveryAddress(message: IdRegistryChangeRecoveryAddressMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: ID_REGISTRY_EIP_712_DOMAIN, + types: { ChangeRecoveryAddress: ID_REGISTRY_CHANGE_RECOVERY_ADDRESS_TYPE }, + primaryType: "ChangeRecoveryAddress", + message, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signAdd(message: KeyGatewayAddMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: KEY_GATEWAY_EIP_712_DOMAIN, + types: { Add: KEY_GATEWAY_ADD_TYPE }, + primaryType: "Add", + message: { ...message, key: bytesToHex(message.key) }, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signRemove(message: KeyRegistryRemoveMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: KEY_REGISTRY_EIP_712_DOMAIN, + types: { Remove: KEY_REGISTRY_REMOVE_TYPE }, + primaryType: "Remove", + message: { ...message, key: bytesToHex(message.key) }, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } + + public async signKeyRequest(message: SignedKeyRequestMessage): HubAsyncResult { + const hexSignature = await ResultAsync.fromPromise( + this._viemLocalAccount.signTypedData({ + domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, + types: { SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE }, + primaryType: "SignedKeyRequest", + message: { ...message, key: bytesToHex(message.key) }, + }), + (e) => new HubError("bad_request.invalid_param", e as Error), + ); + return hexSignature.andThen((hex) => hexStringToBytes(hex)); + } }