Skip to content

Commit

Permalink
fix: refactor verifiable presentation signing options into single pag…
Browse files Browse the repository at this point in the history
…e, use dropdown menu (#509)
  • Loading branch information
AndrewCLu committed Sep 13, 2023
1 parent 9e7d236 commit dab5a69
Show file tree
Hide file tree
Showing 18 changed files with 1,008 additions and 726 deletions.
5 changes: 5 additions & 0 deletions packages/app/src/background/cryptKeeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ export default class CryptKeeperController {
this.lockService.ensure,
this.verifiableCredentialsService.generateVerifiablePresentation,
);
this.handler.add(
RPCAction.GENERATE_VERIFIABLE_PRESENTATION_WITH_CRYPTKEEPER,
this.lockService.ensure,
this.verifiableCredentialsService.generateVerifiablePresentationWithCryptkeeper,
);
this.handler.add(
RPCAction.GENERATE_VERIFIABLE_PRESENTATION_REQUEST,
this.lockService.ensure,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { IVerifiableCredential } from "@cryptkeeperzk/types";
/* eslint-disable @typescript-eslint/unbound-method */
import { EventName } from "@cryptkeeperzk/providers";
import browser from "webextension-polyfill";

import VerifiableCredentialsService from "@src/background/services/credentials";
import {
Expand All @@ -7,7 +9,14 @@ import {
serializeVerifiableCredential,
} from "@src/background/services/credentials/utils";
import SimpleStorage from "@src/background/services/storage";
import { ICryptkeeperVerifiableCredential } from "@src/types";
import pushMessage from "@src/util/pushMessage";

import type {
IVerifiablePresentation,
IVerifiableCredential,
IVerifiablePresentationRequest,
} from "@cryptkeeperzk/types";
import type { ICryptkeeperVerifiableCredential } from "@src/types";

jest.mock("@src/background/services/crypto", (): unknown => ({
...jest.requireActual("@src/background/services/crypto"),
Expand All @@ -19,6 +28,15 @@ jest.mock("@src/background/services/crypto", (): unknown => ({
})),
}));

const exampleSignature = "ck-signature";
jest.mock("@src/background/services/wallet", (): unknown => ({
getInstance: jest.fn(() => ({
signMessage: jest.fn(() => Promise.resolve(exampleSignature)),
})),
}));

jest.mock("@src/util/pushMessage");

jest.mock("@src/background/services/storage");

interface MockStorage {
Expand Down Expand Up @@ -86,9 +104,28 @@ describe("background/services/credentials", () => {
credentialsMap.set(exampleCredentialHashTwo, exampleCryptkeeperCredentialStringTwo);
const credentialsStorageString = JSON.stringify(Array.from(credentialsMap));

const exampleVerifiablePresentationRequest: IVerifiablePresentationRequest = {
request: "example request",
};
const exampleVerifiablePresentation: IVerifiablePresentation = {
context: ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiablePresentation"],
verifiableCredential: [exampleCredential],
};

const defaultTabs = [{ id: 1 }];

const defaultPopupTab = { id: 1, active: true, highlighted: true };

const verifiableCredentialsService = VerifiableCredentialsService.getInstance();

beforeEach(() => {
(browser.tabs.create as jest.Mock).mockResolvedValue(defaultPopupTab);

(browser.tabs.query as jest.Mock).mockResolvedValue(defaultTabs);

(browser.tabs.sendMessage as jest.Mock).mockRejectedValueOnce(false).mockResolvedValue(true);

(SimpleStorage as jest.Mock).mock.instances.forEach((instance: MockStorage) => {
instance.get.mockReturnValue(credentialsStorageString);
instance.set.mockReturnValue(undefined);
Expand All @@ -102,6 +139,95 @@ describe("background/services/credentials", () => {
instance.set.mockClear();
instance.clear.mockClear();
});

(pushMessage as jest.Mock).mockClear();

(browser.tabs.sendMessage as jest.Mock).mockClear();
});

describe("add and reject verifiable credential requests", () => {
test("should successfully create an add verifiable credential request", async () => {
await verifiableCredentialsService.addVerifiableCredentialRequest(exampleCredentialString);

expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });

const defaultOptions = {
tabId: defaultPopupTab.id,
type: "popup",
focused: true,
width: 385,
height: 610,
};

expect(browser.windows.create).toBeCalledWith(defaultOptions);
});

test("should successfully reject a verifiable credential request", async () => {
await verifiableCredentialsService.rejectVerifiableCredentialRequest();

expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
expect(browser.tabs.sendMessage).toBeCalledWith(defaultTabs[0].id, {
type: EventName.REJECT_VERIFIABLE_CREDENTIAL,
});
});
});

describe("generate verifiable presentations", () => {
test("should successfully create a generate verifiable presentation request", async () => {
await verifiableCredentialsService.generateVerifiablePresentationRequest(exampleVerifiablePresentationRequest);

expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });

const defaultOptions = {
tabId: defaultPopupTab.id,
type: "popup",
focused: true,
width: 385,
height: 610,
};

expect(browser.windows.create).toBeCalledWith(defaultOptions);
});

test("should successfully generate a verifiable presentation", async () => {
await verifiableCredentialsService.generateVerifiablePresentation(exampleVerifiablePresentation);

expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
expect(browser.tabs.sendMessage).toBeCalledWith(defaultTabs[0].id, {
type: EventName.GENERATE_VERIFIABLE_PRESENTATION,
payload: { verifiablePresentation: exampleVerifiablePresentation },
});
});

test("should successfully generate a verifiable presentation with cryptkeeper", async () => {
const exampleAddress = "0x123";
const ETHEREUM_SIGNATURE_SPECIFICATION_TYPE = "EthereumEip712Signature2021";
const VERIFIABLE_CREDENTIAL_PROOF_PURPOSE = "assertionMethod";

await verifiableCredentialsService.generateVerifiablePresentationWithCryptkeeper({
verifiablePresentation: exampleVerifiablePresentation,
address: exampleAddress,
});

const signedVerifiablePresentation = {
...exampleVerifiablePresentation,
proof: [
{
type: [ETHEREUM_SIGNATURE_SPECIFICATION_TYPE],
proofPurpose: VERIFIABLE_CREDENTIAL_PROOF_PURPOSE,
verificationMethod: exampleAddress,
created: new Date(),
proofValue: exampleSignature,
},
],
};

expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
expect(browser.tabs.sendMessage).toBeCalledWith(defaultTabs[0].id, {
type: EventName.GENERATE_VERIFIABLE_PRESENTATION,
payload: { verifiablePresentation: signedVerifiablePresentation },
});
});
});

describe("add and retrieve verifiable credentials", () => {
Expand Down
56 changes: 55 additions & 1 deletion packages/app/src/background/services/credentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import CryptoService, { ECryptMode } from "@src/background/services/crypto";
import HistoryService from "@src/background/services/history";
import NotificationService from "@src/background/services/notification";
import SimpleStorage from "@src/background/services/storage";
import WalletService from "@src/background/services/wallet";
import { Paths } from "@src/constants";
import { OperationType, IRenameVerifiableCredentialArgs, ICryptkeeperVerifiableCredential } from "@src/types";
import { IAddVerifiableCredentialArgs } from "@src/types/verifiableCredentials";
import {
IAddVerifiableCredentialArgs,
IGenerateVerifiablePresentationWithCryptkeeperArgs,
} from "@src/types/verifiableCredentials";

import type { IVerifiablePresentation, IVerifiablePresentationRequest } from "@cryptkeeperzk/types";
import type { BackupData, IBackupable } from "@src/background/services/backup";
Expand All @@ -19,9 +23,12 @@ import {
deserializeVerifiableCredential,
deserializeCryptkeeperVerifiableCredential,
validateSerializedVerifiableCredential,
serializeVerifiablePresentation,
} from "./utils";

const VERIFIABLE_CREDENTIALS_KEY = "@@VERIFIABLE-CREDENTIALS@@";
const ETHEREUM_SIGNATURE_SPECIFICATION_TYPE = "EthereumEip712Signature2021";
const VERIFIABLE_CREDENTIAL_PROOF_PURPOSE = "assertionMethod";

export default class VerifiableCredentialsService implements IBackupable {
private static INSTANCE?: VerifiableCredentialsService;
Expand All @@ -30,6 +37,8 @@ export default class VerifiableCredentialsService implements IBackupable {

private cryptoService: CryptoService;

private walletService: WalletService;

private historyService: HistoryService;

private notificationService: NotificationService;
Expand All @@ -39,6 +48,7 @@ export default class VerifiableCredentialsService implements IBackupable {
private constructor() {
this.verifiableCredentialsStore = new SimpleStorage(VERIFIABLE_CREDENTIALS_KEY);
this.cryptoService = CryptoService.getInstance();
this.walletService = WalletService.getInstance();
this.historyService = HistoryService.getInstance();
this.notificationService = NotificationService.getInstance();
this.browserController = BrowserUtils.getInstance();
Expand Down Expand Up @@ -232,6 +242,50 @@ export default class VerifiableCredentialsService implements IBackupable {
);
};

generateVerifiablePresentationWithCryptkeeper = async ({
verifiablePresentation,
address,
}: IGenerateVerifiablePresentationWithCryptkeeperArgs): Promise<void> => {
const serializedVerifiablePresentation = serializeVerifiablePresentation(verifiablePresentation);
const signature = await this.walletService.signMessage({
message: serializedVerifiablePresentation,
address,
});
const signedVerifiablePresentation = {
...verifiablePresentation,
proof: [
{
type: [ETHEREUM_SIGNATURE_SPECIFICATION_TYPE],
proofPurpose: VERIFIABLE_CREDENTIAL_PROOF_PURPOSE,
verificationMethod: address,
created: new Date(),
proofValue: signature,
},
],
};

await this.historyService.trackOperation(OperationType.GENERATE_VERIFIABLE_PRESENTATION, {});
await this.notificationService.create({
options: {
title: "Verifiable Presentation generated.",
message: `Generated 1 Verifiable Presentation.`,
iconUrl: browser.runtime.getURL("/icons/logo.png"),
type: "basic",
},
});
const tabs = await browser.tabs.query({ active: true });
await Promise.all(
tabs.map((tab) =>
browser.tabs
.sendMessage(tab.id!, {
type: EventName.GENERATE_VERIFIABLE_PRESENTATION,
payload: { verifiablePresentation: signedVerifiablePresentation },
})
.catch(() => undefined),
),
);
};

rejectVerifiablePresentationRequest = async (): Promise<void> => {
await this.historyService.trackOperation(OperationType.REJECT_VERIFIABLE_PRESENTATION_REQUEST, {});
await this.notificationService.create({
Expand Down
9 changes: 5 additions & 4 deletions packages/app/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ export { ConnectorNames, type IUseWalletData } from "./hooks";
export { type ISecretArgs, type ICheckPasswordArgs } from "./lock";
export { InitializationStep } from "./misc";
export { type DeferredPromise } from "./utility";
export {
type IVerifiableCredentialMetadata,
type ICryptkeeperVerifiableCredential,
type IRenameVerifiableCredentialArgs,
export type {
IVerifiableCredentialMetadata,
ICryptkeeperVerifiableCredential,
IRenameVerifiableCredentialArgs,
IGenerateVerifiablePresentationWithCryptkeeperArgs,
} from "./verifiableCredentials";
export { type ISignMessageArgs, type ICheckMnemonicArgs } from "./wallet";
7 changes: 6 additions & 1 deletion packages/app/src/types/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IVerifiableCredential } from "@cryptkeeperzk/types";
import type { IVerifiableCredential, IVerifiablePresentation } from "@cryptkeeperzk/types";

export interface IVerifiableCredentialMetadata {
name: string;
Expand All @@ -19,3 +19,8 @@ export interface IRenameVerifiableCredentialArgs {
verifiableCredentialHash: string;
newVerifiableCredentialName: string;
}

export interface IGenerateVerifiablePresentationWithCryptkeeperArgs {
verifiablePresentation: IVerifiablePresentation;
address: string;
}
Loading

0 comments on commit dab5a69

Please sign in to comment.