diff --git a/packages/app/package.json b/packages/app/package.json index 8eb5f28a7..43e5dd4c9 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -119,6 +119,7 @@ "eventemitter2": "^6.4.9", "extension-port-stream": "^2.1.1", "fast-deep-equal": "^3.1.3", + "idc-nullifier": "^0.0.4", "json-stable-stringify": "^1.0.2", "link-preview-js": "^3.0.5", "lodash": "^4.17.21", diff --git a/packages/app/src/background/services/zkIdentity/__tests__/zkIdentity.test.ts b/packages/app/src/background/services/zkIdentity/__tests__/zkIdentity.test.ts index 455aaeaea..a94fe8ac5 100644 --- a/packages/app/src/background/services/zkIdentity/__tests__/zkIdentity.test.ts +++ b/packages/app/src/background/services/zkIdentity/__tests__/zkIdentity.test.ts @@ -1,6 +1,11 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { EventName } from "@cryptkeeperzk/providers"; -import { EWallet, ConnectedIdentityMetadata, ICreateIdentityOptions, IImportIdentityArgs } from "@cryptkeeperzk/types"; +import { + EWallet, + type ConnectedIdentityMetadata, + type ICreateIdentityOptions, + type IImportIdentityArgs, +} from "@cryptkeeperzk/types"; import { createNewIdentity } from "@cryptkeeperzk/zk"; import pick from "lodash/pick"; import browser from "webextension-polyfill"; @@ -643,6 +648,7 @@ describe("background/services/zkIdentity", () => { name: "Name", nullifier: mockDefaultNullifier, trapdoor: mockDefaultTrapdoor, + messageSignature: "signature", urlOrigin: "http://localhost:3000", }; diff --git a/packages/app/src/background/services/zkIdentity/index.ts b/packages/app/src/background/services/zkIdentity/index.ts index f824b2902..7e4a33b9b 100644 --- a/packages/app/src/background/services/zkIdentity/index.ts +++ b/packages/app/src/background/services/zkIdentity/index.ts @@ -15,6 +15,8 @@ import { } from "@cryptkeeperzk/types"; import { ZkIdentitySemaphore, createNewIdentity } from "@cryptkeeperzk/zk"; import { bigintToHex } from "bigint-conversion"; +// import { Prover } from "idc-nullifier"; +import omit from "lodash/omit"; import pick from "lodash/pick"; import browser from "webextension-polyfill"; @@ -58,6 +60,8 @@ export default class ZkIdentityService extends BaseService implements IBackupabl private lockService: LockerService; + // private idcProver: Prover; + private connectedIdentity?: ZkIdentitySemaphore; private constructor() { @@ -65,6 +69,7 @@ export default class ZkIdentityService extends BaseService implements IBackupabl this.connectedIdentity = undefined; this.identitiesStore = new SimpleStorage(IDENTITY_KEY); this.connectedIdentityStore = new SimpleStorage(CONNECTED_IDENTITY_KEY); + // this.idcProver = new Prover(); this.notificationService = NotificationService.getInstance(); this.historyService = HistoryService.getInstance(); this.browserController = BrowserUtils.getInstance(); @@ -343,7 +348,19 @@ export default class ZkIdentityService extends BaseService implements IBackupabl }; import = async (args: IImportIdentityArgs): Promise => { - const identity = createNewIdentity({ ...args, groups: [], isDeterministic: false }); + // const importedIdentity = createNewIdentity({ ...args, groups: [], isDeterministic: false, isImported: true }); + const identity = createNewIdentity({ + ...omit(args, ["trapdoor", "nullifier"]), + groups: [], + isDeterministic: true, + isImported: true, + }); + // const proof = this.idcProver.generateProof({ + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-expect-error + // identity: importedIdentity.zkIdentity, + // externalNullifier: identity.genIdentityCommitment(), + // }); const status = await this.insertIdentity(identity); if (!status) { @@ -369,6 +386,7 @@ export default class ZkIdentityService extends BaseService implements IBackupabl urlOrigin, isDeterministic, nonce: isDeterministic ? options.nonce : undefined, + isImported: false, name: options.name || `Account # ${numOfIdentities}`, messageSignature, }; diff --git a/packages/app/src/config/mock/wallet.ts b/packages/app/src/config/mock/wallet.ts index b0b73a330..16dc733e2 100644 --- a/packages/app/src/config/mock/wallet.ts +++ b/packages/app/src/config/mock/wallet.ts @@ -1,8 +1,9 @@ import BigNumber from "bignumber.js"; import { mockConnector } from "@src/connectors/mock"; -import { ConnectorNames, IUseWalletData } from "@src/types"; +import { ConnectorNames, type IUseWalletData } from "@src/types"; +import type { IUseSignatureOptionsData } from "@src/ui/hooks/wallet/useSignatureOptions"; import type { BrowserProvider } from "ethers"; import { ZERO_ADDRESS } from "../const"; @@ -29,3 +30,14 @@ export const defaultWalletHookData: IUseWalletData = { onDisconnect: jest.fn(), onLock: jest.fn(), }; + +export const mockSignatureOptions: IUseSignatureOptionsData = { + options: [ + { id: "ck", title: "Sign with CryptKeeper", checkDisabledItem: () => false }, + { + id: "eth", + title: "Sign with MetaMask", + checkDisabledItem: () => false, + }, + ], +}; diff --git a/packages/app/src/ui/components/DropdownButton/DropdownButton.tsx b/packages/app/src/ui/components/DropdownButton/DropdownButton.tsx index e11a45bb3..51b23eb1a 100644 --- a/packages/app/src/ui/components/DropdownButton/DropdownButton.tsx +++ b/packages/app/src/ui/components/DropdownButton/DropdownButton.tsx @@ -1,6 +1,6 @@ import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import Button from "@mui/material/Button"; -import ButtonGroup from "@mui/material/ButtonGroup"; +import ButtonGroup, { type ButtonGroupProps } from "@mui/material/ButtonGroup"; import ClickAwayListener from "@mui/material/ClickAwayListener"; import Grow from "@mui/material/Grow"; import MenuItem from "@mui/material/MenuItem"; @@ -11,27 +11,34 @@ import Typography from "@mui/material/Typography"; import { useDropdownButton } from "./useDropdownButton"; -export interface IDropdownButtonProps { - menuOptions: IDropdownButtonMenuItem[]; +export interface IDropdownButtonProps extends Omit { + options: IDropdownButtonOption[]; + disabled?: boolean; onClick: (index: number) => void; } -export interface IDropdownButtonMenuItem { +export interface IDropdownButtonOption { id: string; title: string; checkDisabledItem?: (index: number) => boolean; } -const DropdownButton = ({ menuOptions, onClick }: IDropdownButtonProps): JSX.Element => { +const DropdownButton = ({ disabled = false, options, onClick, ...rest }: IDropdownButtonProps): JSX.Element => { const { isMenuOpen, menuRef, selectedIndex, onToggleMenu, onMenuItemClick, onSubmit } = useDropdownButton({ onClick, }); return ( <> - - - + - + ); diff --git a/packages/app/src/ui/pages/ImportIdentity/__tests__/ImportIdentity.test.tsx b/packages/app/src/ui/pages/ImportIdentity/__tests__/ImportIdentity.test.tsx index d079cee5b..806ed0d4b 100644 --- a/packages/app/src/ui/pages/ImportIdentity/__tests__/ImportIdentity.test.tsx +++ b/packages/app/src/ui/pages/ImportIdentity/__tests__/ImportIdentity.test.tsx @@ -5,7 +5,9 @@ import { render, waitFor } from "@testing-library/react"; import { Suspense } from "react"; +import { mockSignatureOptions } from "@src/config/mock/wallet"; import { mockDefaultIdentity, mockDefaultIdentityCommitment, mockDefaultIdentitySecret } from "@src/config/mock/zk"; +import { useSignatureOptions } from "@src/ui/hooks/wallet"; import ImportIdentity from ".."; import { IUseImportIdentityData, useImportIdentity } from "../useImportIdentity"; @@ -14,6 +16,10 @@ jest.mock("bigint-conversion", (): unknown => ({ bigintToHex: jest.fn(), })); +jest.mock("@src/ui/hooks/wallet", (): unknown => ({ + useSignatureOptions: jest.fn(), +})); + jest.mock("../useImportIdentity", (): unknown => ({ useImportIdentity: jest.fn(), })); @@ -36,6 +42,8 @@ describe("ui/pages/ImportIdentity", () => { beforeEach(() => { (useImportIdentity as jest.Mock).mockReturnValue(defaultHookData); + + (useSignatureOptions as jest.Mock).mockReturnValue(mockSignatureOptions); }); afterEach(() => { diff --git a/packages/app/src/ui/pages/ImportIdentity/__tests__/useImportIdentity.test.ts b/packages/app/src/ui/pages/ImportIdentity/__tests__/useImportIdentity.test.ts index 68787c4da..aaf44ed6c 100644 --- a/packages/app/src/ui/pages/ImportIdentity/__tests__/useImportIdentity.test.ts +++ b/packages/app/src/ui/pages/ImportIdentity/__tests__/useImportIdentity.test.ts @@ -2,6 +2,7 @@ * @jest-environment jsdom */ +import { EWallet } from "@cryptkeeperzk/types"; import { calculateIdentityCommitment, calculateIdentitySecret } from "@cryptkeeperzk/zk"; import { act, renderHook, waitFor } from "@testing-library/react"; import { getLinkPreview } from "link-preview-js"; @@ -14,6 +15,7 @@ import { mockIdenityPrivateJsonFile, mockJsonFile, } from "@src/config/mock/file"; +import { defaultWalletHookData } from "@src/config/mock/wallet"; import { mockDefaultIdentity, mockDefaultIdentityCommitment, @@ -27,6 +29,8 @@ import { useAppDispatch } from "@src/ui/ducks/hooks"; import { importIdentity } from "@src/ui/ducks/identities"; import { rejectUserRequest } from "@src/ui/ducks/requests"; import { useSearchParam } from "@src/ui/hooks/url"; +import { useCryptKeeperWallet, useEthWallet } from "@src/ui/hooks/wallet"; +import { getImportMessageTemplate, signWithSigner } from "@src/ui/services/identity"; import { redirectToNewTab } from "@src/util/browser"; import { useImportIdentity } from "../useImportIdentity"; @@ -44,6 +48,11 @@ jest.mock("link-preview-js", (): unknown => ({ getLinkPreview: jest.fn(), })); +jest.mock("@src/ui/services/identity", (): unknown => ({ + signWithSigner: jest.fn(), + getImportMessageTemplate: jest.fn(), +})); + jest.mock("@src/ui/hooks/url", (): unknown => ({ useSearchParam: jest.fn(), })); @@ -52,6 +61,11 @@ jest.mock("@src/util/browser", (): unknown => ({ redirectToNewTab: jest.fn(), })); +jest.mock("@src/ui/hooks/wallet", (): unknown => ({ + useEthWallet: jest.fn(), + useCryptKeeperWallet: jest.fn(), +})); + jest.mock("@src/ui/ducks/app", (): unknown => ({ closePopup: jest.fn(), })); @@ -80,6 +94,8 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => { trapdoor: mockDefaultTrapdoor, nullifier: mockDefaultNullifier, }; + const mockSignedMessage = "signed-message"; + const mockMessage = "message"; const mockNavigate = jest.fn(); const mockDispatch = jest.fn(() => Promise.resolve(false)); @@ -93,6 +109,14 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => { (useSearchParam as jest.Mock).mockImplementation((arg: keyof typeof defaultUrlParams) => defaultUrlParams[arg]); + (signWithSigner as jest.Mock).mockResolvedValue(mockSignedMessage); + + (getImportMessageTemplate as jest.Mock).mockReturnValue(mockMessage); + + (useEthWallet as jest.Mock).mockReturnValue({ ...defaultWalletHookData, isActive: true }); + + (useCryptKeeperWallet as jest.Mock).mockReturnValue({ ...defaultWalletHookData, isActive: true }); + (calculateIdentitySecret as jest.Mock).mockReturnValue(mockDefaultIdentitySecret); (calculateIdentityCommitment as jest.Mock).mockReturnValue(mockDefaultIdentityCommitment); @@ -223,11 +247,63 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => { ); }); - test("should submit properly", async () => { + test("should submit with cryptkeeper properly", async () => { + const { result } = renderHook(() => useImportIdentity()); + + await act(() => result.current.register("name").onChange({ target: { value: "name" } })); + await act(() => Promise.resolve(result.current.onSubmit(EWallet.CRYPTKEEPER_WALLET))); + + expect(signWithSigner).toBeCalledTimes(0); + expect(mockDispatch).toBeCalledTimes(2); + expect(importIdentity).toBeCalledTimes(1); + expect(closePopup).toBeCalledTimes(1); + expect(mockNavigate).toBeCalledTimes(1); + expect(mockNavigate).toBeCalledWith(Paths.HOME); + }); + + test("should connect eth wallet properly", async () => { + (useEthWallet as jest.Mock).mockReturnValue({ ...defaultWalletHookData, isActive: false }); + const { result } = renderHook(() => useImportIdentity()); + + await act(async () => Promise.resolve(result.current.onSubmit(EWallet.ETH_WALLET))); + + expect(result.current.isLoading).toBe(false); + expect(defaultWalletHookData.onConnect).toBeCalledTimes(1); + }); + + test("should handle error when trying to connect with eth wallet", async () => { + (useEthWallet as jest.Mock).mockReturnValue({ + ...defaultWalletHookData, + isActive: false, + onConnect: jest.fn(() => Promise.reject()), + }); + + const { result } = renderHook(() => useImportIdentity()); + + await act(async () => Promise.resolve(result.current.onSubmit(EWallet.ETH_WALLET))); + + expect(result.current.isLoading).toBe(false); + expect(result.current.errors.root).toBe("Wallet connection error"); + }); + + test("should handle error when trying to sign with eth wallet", async () => { + const error = new Error("error"); + (signWithSigner as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useImportIdentity()); + + await act(async () => Promise.resolve(result.current.onSubmit(EWallet.ETH_WALLET))); + + expect(mockDispatch).toBeCalledTimes(0); + expect(result.current.isLoading).toBe(false); + expect(result.current.errors.root).toBe(error.message); + }); + + test("should submit with eth wallet properly", async () => { const { result } = renderHook(() => useImportIdentity()); await act(() => result.current.register("name").onChange({ target: { value: "name" } })); - await act(() => Promise.resolve(result.current.onSubmit())); + await act(() => Promise.resolve(result.current.onSubmit(EWallet.ETH_WALLET))); expect(mockDispatch).toBeCalledTimes(2); expect(importIdentity).toBeCalledTimes(1); @@ -244,7 +320,7 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => { const { result } = renderHook(() => useImportIdentity()); await act(() => result.current.register("name").onChange({ target: { value: "name" } })); - await act(() => Promise.resolve(result.current.onSubmit())); + await act(() => Promise.resolve(result.current.onSubmit(EWallet.CRYPTKEEPER_WALLET))); expect(mockDispatch).toBeCalledTimes(1); expect(importIdentity).toBeCalledTimes(1); @@ -262,7 +338,7 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => { const { result } = renderHook(() => useImportIdentity()); await act(() => result.current.register("name").onChange({ target: { value: "name" } })); - await act(() => Promise.resolve(result.current.onSubmit())); + await act(() => Promise.resolve(result.current.onSubmit(EWallet.CRYPTKEEPER_WALLET))); expect(mockDispatch).toBeCalledTimes(1); expect(importIdentity).toBeCalledTimes(1); @@ -276,9 +352,18 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => { const { result } = renderHook(() => useImportIdentity()); await act(() => result.current.register("name").onChange({ target: { value: "name" } })); - await act(() => Promise.resolve(result.current.onSubmit())); + await act(() => Promise.resolve(result.current.onSubmit(EWallet.CRYPTKEEPER_WALLET))); await waitFor(() => result.current.errors.root); expect(result.current.errors.root).toBe(error.message); }); + + test("should handle unknown signature option properly", async () => { + const { result } = renderHook(() => useImportIdentity()); + + await act(async () => Promise.resolve(result.current.onSubmit(9000))); + + expect(result.current.isLoading).toBe(false); + expect(mockDispatch).toBeCalledTimes(0); + }); }); diff --git a/packages/app/src/ui/pages/ImportIdentity/useImportIdentity.ts b/packages/app/src/ui/pages/ImportIdentity/useImportIdentity.ts index 6cb5a764f..729c97434 100644 --- a/packages/app/src/ui/pages/ImportIdentity/useImportIdentity.ts +++ b/packages/app/src/ui/pages/ImportIdentity/useImportIdentity.ts @@ -1,4 +1,5 @@ import { EventName } from "@cryptkeeperzk/providers"; +import { EWallet } from "@cryptkeeperzk/types"; import { calculateIdentityCommitment, calculateIdentitySecret } from "@cryptkeeperzk/zk"; import get from "lodash/get"; import { useCallback, useMemo } from "react"; @@ -13,6 +14,8 @@ import { importIdentity } from "@src/ui/ducks/identities"; import { rejectUserRequest } from "@src/ui/ducks/requests"; import { useSearchParam } from "@src/ui/hooks/url"; import { useValidationResolver } from "@src/ui/hooks/validation"; +import { useCryptKeeperWallet, useEthWallet } from "@src/ui/hooks/wallet"; +import { getImportMessageTemplate, signWithSigner } from "@src/ui/services/identity"; import { redirectToNewTab } from "@src/util/browser"; import { readFile } from "@src/util/file"; import { checkBigNumber, convertFromHexToDec } from "@src/util/numbers"; @@ -37,7 +40,7 @@ export interface IUseImportIdentityData { onGoBack: () => void; onGoToHost: () => void; onDrop: onDropCallback; - onSubmit: () => void; + onSubmit: (option: number) => void; } interface FormFields { @@ -67,6 +70,9 @@ export const useImportIdentity = (): IUseImportIdentityData => { const dispatch = useAppDispatch(); const navigate = useNavigate(); + const ethWallet = useEthWallet(); + const cryptKeeperWallet = useCryptKeeperWallet(); + const back = useSearchParam("back"); const urlOrigin = useSearchParam("urlOrigin"); const trapdoorUrlParam = useSearchParam("trapdoor"); @@ -159,9 +165,28 @@ export const useImportIdentity = (): IUseImportIdentityData => { [setValue, setError, clearErrors], ); - const onSubmit = useCallback( - (data: FormFields) => { - dispatch(importIdentity({ ...data, urlOrigin })) + const importNewIdentity = useCallback( + async (data: FormFields, walletType: EWallet) => { + const account = + walletType === EWallet.ETH_WALLET ? ethWallet.address?.toLowerCase() : cryptKeeperWallet.address?.toLowerCase(); + const message = getImportMessageTemplate({ + trapdoor: data.trapdoor, + nullifier: data.nullifier, + account: account!, + }); + + const messageSignature = + walletType === EWallet.ETH_WALLET + ? await signWithSigner({ signer: await ethWallet.provider?.getSigner(), message }).catch((error: Error) => { + setError("root", { message: error.message }); + }) + : ""; + + if (messageSignature === undefined) { + return; + } + + dispatch(importIdentity({ ...data, messageSignature, urlOrigin })) .then(() => { if (redirect === Paths.CREATE_IDENTITY.toString()) { navigate(Paths.HOME); @@ -177,7 +202,51 @@ export const useImportIdentity = (): IUseImportIdentityData => { setError("root", { message: error.message }); }); }, - [redirect, redirectUrl, urlOrigin, setError, dispatch], + [ + ethWallet.address, + ethWallet.provider, + cryptKeeperWallet.address, + redirectUrl, + urlOrigin, + dispatch, + navigate, + setError, + ], + ); + + const onImportIdentityWithEthWallet = useCallback( + async (data: FormFields) => importNewIdentity(data, EWallet.ETH_WALLET), + [ethWallet.isActive, importNewIdentity], + ); + + const onImportIdentityWithCryptkeeper = useCallback( + async (data: FormFields) => importNewIdentity(data, EWallet.CRYPTKEEPER_WALLET), + [cryptKeeperWallet.isActive, importNewIdentity], + ); + + const onConnectWallet = useCallback(async () => { + await ethWallet.onConnect().catch(() => { + setError("root", { type: "submit", message: "Wallet connection error" }); + }); + }, [setError, ethWallet.onConnect]); + + const onSubmit = useCallback( + (index: number) => { + const option = index as EWallet; + + switch (true) { + case option === EWallet.CRYPTKEEPER_WALLET: + return handleSubmit(onImportIdentityWithCryptkeeper)(); + case option === EWallet.ETH_WALLET && !ethWallet.isActive: + return handleSubmit(onConnectWallet)(); + case option === EWallet.ETH_WALLET && ethWallet.isActive: + return handleSubmit(onImportIdentityWithEthWallet)(); + + default: + return undefined; + } + }, + [ethWallet.isActive, handleSubmit, onConnectWallet, onImportIdentityWithEthWallet, onImportIdentityWithCryptkeeper], ); return { @@ -197,6 +266,6 @@ export const useImportIdentity = (): IUseImportIdentityData => { onGoBack, onGoToHost, onDrop, - onSubmit: handleSubmit(onSubmit), + onSubmit, }; }; diff --git a/packages/app/src/ui/pages/PresentVerifiableCredential/PresentVerifiableCredential.tsx b/packages/app/src/ui/pages/PresentVerifiableCredential/PresentVerifiableCredential.tsx index f367791d0..b52b19fd9 100644 --- a/packages/app/src/ui/pages/PresentVerifiableCredential/PresentVerifiableCredential.tsx +++ b/packages/app/src/ui/pages/PresentVerifiableCredential/PresentVerifiableCredential.tsx @@ -102,7 +102,7 @@ const PresentVerifiableCredential = (): JSX.Element => { Reject - + diff --git a/packages/app/src/ui/services/identity/__tests__/identity.test.ts b/packages/app/src/ui/services/identity/__tests__/identity.test.ts index 890abceff..8ce099732 100644 --- a/packages/app/src/ui/services/identity/__tests__/identity.test.ts +++ b/packages/app/src/ui/services/identity/__tests__/identity.test.ts @@ -1,8 +1,9 @@ import { ZERO_ADDRESS } from "@src/config/const"; +import { mockDefaultNullifier, mockDefaultTrapdoor } from "@src/config/mock/zk"; import type { JsonRpcSigner } from "ethers"; -import { getMessageTemplate, signWithSigner } from ".."; +import { getImportMessageTemplate, getMessageTemplate, signWithSigner } from ".."; describe("ui/services/identity", () => { test("should sign message properly", async () => { @@ -26,6 +27,28 @@ describe("ui/services/identity", () => { expect(result).toBe("signed"); }); + test("should sign import message properly", async () => { + const mockSigner = { + signMessage: jest.fn().mockResolvedValue("signed"), + }; + + const message = getImportMessageTemplate({ + account: ZERO_ADDRESS, + trapdoor: mockDefaultTrapdoor, + nullifier: mockDefaultNullifier, + }); + const result = await signWithSigner({ + message, + signer: mockSigner as unknown as JsonRpcSigner, + }); + + expect(mockSigner.signMessage).toBeCalledTimes(1); + expect(mockSigner.signMessage).toBeCalledWith( + `Sign this message with account ${ZERO_ADDRESS} to import your Semaphore identity with trapdoor: ${mockDefaultTrapdoor}, nullifier: ${mockDefaultNullifier}`, + ); + expect(result).toBe("signed"); + }); + test("should throw user rejected error", async () => { const mockSigner = { signMessage: jest.fn().mockRejectedValue({ message: "user rejected signing", code: "ACTION_REJECTED" }), diff --git a/packages/app/src/ui/services/identity/index.ts b/packages/app/src/ui/services/identity/index.ts index 0e2b2c71d..b4a86572d 100644 --- a/packages/app/src/ui/services/identity/index.ts +++ b/packages/app/src/ui/services/identity/index.ts @@ -26,3 +26,14 @@ export function getMessageTemplate({ account, nonce }: IGetMessageTemplateArgs): const nonceEnd = `with key nonce: ${nonce}`; return `Sign this message with account ${account} to generate your Semaphore identity ${nonceEnd}`.trim(); } + +export interface IGetImportMessageTemplateArgs { + account: string; + trapdoor: string; + nullifier: string; +} + +export function getImportMessageTemplate({ account, trapdoor, nullifier }: IGetImportMessageTemplateArgs): string { + const end = `with trapdoor: ${trapdoor}, nullifier: ${nullifier}`; + return `Sign this message with account ${account} to import your Semaphore identity ${end}`.trim(); +} diff --git a/packages/types/src/identity/index.ts b/packages/types/src/identity/index.ts index fc8fc1a31..75b3dbc7a 100644 --- a/packages/types/src/identity/index.ts +++ b/packages/types/src/identity/index.ts @@ -31,12 +31,13 @@ export interface IImportIdentityArgs { name: string; trapdoor: string; nullifier: string; + messageSignature: string; urlOrigin?: string; } export enum EWallet { - ETH_WALLET, CRYPTKEEPER_WALLET, + ETH_WALLET, } export interface IIdentityMetadata { @@ -89,6 +90,7 @@ export interface ICreateIdentityArgs { name: string; groups: IGroupData[]; isDeterministic: boolean; + isImported: boolean; account?: string; messageSignature?: string; nonce?: number; diff --git a/packages/zk/src/identity/factory/index.ts b/packages/zk/src/identity/factory/index.ts index da2acaaa7..cd8f33f4f 100644 --- a/packages/zk/src/identity/factory/index.ts +++ b/packages/zk/src/identity/factory/index.ts @@ -4,7 +4,18 @@ import { ICreateIdentityArgs } from "@cryptkeeperzk/types"; import { ZkIdentitySemaphore } from "../protocols"; export function createNewIdentity(config: ICreateIdentityArgs): ZkIdentitySemaphore { - const { name, messageSignature, account, groups, urlOrigin, isDeterministic, nonce, trapdoor, nullifier } = config; + const { + name, + messageSignature, + account, + groups, + urlOrigin, + isDeterministic, + isImported, + nonce, + trapdoor, + nullifier, + } = config; const serialized = trapdoor && nullifier ? JSON.stringify([trapdoor, nullifier]) : undefined; const identity = new Identity(serialized || messageSignature); @@ -16,6 +27,6 @@ export function createNewIdentity(config: ICreateIdentityArgs): ZkIdentitySemaph nonce, urlOrigin, isDeterministic, - isImported: Boolean(serialized), + isImported, }); } diff --git a/packages/zk/src/proof/protocols/__tests__/utils.test.ts b/packages/zk/src/proof/protocols/__tests__/utils.test.ts index b84bf4d78..6634f53ed 100644 --- a/packages/zk/src/proof/protocols/__tests__/utils.test.ts +++ b/packages/zk/src/proof/protocols/__tests__/utils.test.ts @@ -1,6 +1,7 @@ import { Identity } from "@cryptkeeperzk/semaphore-identity"; import { MerkleProof } from "@zk-kit/incremental-merkle-tree"; -import { poseidon1, poseidon2 } from "poseidon-lite"; +import { poseidon1 } from "poseidon-lite/poseidon1"; +import { poseidon2 } from "poseidon-lite/poseidon2"; import { deserializeMerkleProof, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a544e42f..ced6dcf4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,6 +191,9 @@ importers: fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 + idc-nullifier: + specifier: ^0.0.4 + version: 0.0.4 json-stable-stringify: specifier: ^1.0.2 version: 1.0.2 @@ -7497,6 +7500,17 @@ packages: ffjavascript: 0.2.59 dev: false + /circom_runtime@0.1.24: + resolution: {integrity: sha512-H7/7I2J/cBmRnZm9docOCGhfxzS61BEm4TMCWcrZGsWNBQhePNfQq88Oj2XpUfzmBTCd8pRvRb3Mvazt3TMrJw==} + hasBin: true + dependencies: + ffjavascript: 0.2.60 + dev: false + + /circomlib@2.0.5: + resolution: {integrity: sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A==} + dev: false + /circomlibjs@0.0.8: resolution: {integrity: sha512-oZFYapLO0mfiA+i2GU/V7bRNEEPjVcwV4M444nU5lNsdSJpqLwD57m9zxTD5m/KeY7WQ3lEAC9NNKEPQHu7s1w==} dependencies: @@ -10120,6 +10134,14 @@ packages: web-worker: 1.2.0 dev: false + /ffjavascript@0.2.60: + resolution: {integrity: sha512-T/9bnEL5xAZRDbQoEMf+pM9nrhK+C3JyZNmqiWub26EQorW7Jt+jR54gpqDhceA4Nj0YctPQwYnl8xa52/A26A==} + dependencies: + wasmbuilder: 0.0.16 + wasmcurves: 0.2.2 + web-worker: 1.2.0 + dev: false + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -11314,6 +11336,15 @@ packages: postcss: 8.4.24 dev: true + /idc-nullifier@0.0.4: + resolution: {integrity: sha512-kQvqvagzSbrMDcwmmHqw7sF6G0GHqQLJqyec/ie3v91Bo8Sjp/zXOczbE+4b2hGHUo8CnMaCL+P+ozKPh02pKg==} + dependencies: + '@semaphore-protocol/identity': 3.10.1 + circomlib: 2.0.5 + poseidon-lite: 0.2.0 + snarkjs: 0.7.1 + dev: false + /idna-uts46-hx@2.3.1: resolution: {integrity: sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==} engines: {node: '>=4.0.0'} @@ -14836,6 +14867,15 @@ packages: ffjavascript: 0.2.59 dev: false + /r1csfile@0.0.47: + resolution: {integrity: sha512-oI4mAwuh1WwuFg95eJDNDDL8hCaZkwnPuNZrQdLBWvDoRU7EG+L/MOHL7SwPW2Y+ZuYcTLpj3rBkgllBQZN/JA==} + dependencies: + '@iden3/bigarray': 0.0.2 + '@iden3/binfileutils': 0.0.11 + fastfile: 0.0.20 + ffjavascript: 0.2.60 + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -15833,6 +15873,22 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true + /snarkjs@0.7.1: + resolution: {integrity: sha512-Qs1oxssa135WZkzfARgEp5SuKHKvKNtcspeJbE5je6MurUpBylD1rzcAzQSTGWA/EH/BV/TmUyTaTD64xScvbA==} + hasBin: true + dependencies: + '@iden3/binfileutils': 0.0.11 + bfj: 7.0.2 + blake2b-wasm: 2.4.0 + circom_runtime: 0.1.24 + ejs: 3.1.9 + fastfile: 0.0.20 + ffjavascript: 0.2.60 + js-sha3: 0.8.0 + logplease: 1.2.15 + r1csfile: 0.0.47 + dev: false + /sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} dependencies: @@ -17288,6 +17344,12 @@ packages: wasmbuilder: 0.0.16 dev: false + /wasmcurves@0.2.2: + resolution: {integrity: sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==} + dependencies: + wasmbuilder: 0.0.16 + dev: false + /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'}