Skip to content

Commit

Permalink
Merge branch 'master' into chore/upgrade-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
Jayteekay authored May 13, 2024
2 parents 6cc8734 + 2027a1f commit a228021
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 101 deletions.
116 changes: 116 additions & 0 deletions integration-tests/verification-flow/vpi-verification.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {IWallet} from '@docknetwork/wallet-sdk-core/lib/types';
import {
closeWallet,
getCredentialProvider,
getWallet,
} from '../helpers/wallet-helpers';
import {createVerificationController} from '@docknetwork/wallet-sdk-core/src/verification-controller';
import {ProofTemplateIds, createProofRequest} from '../helpers/certs-helpers';

const credential = {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
"https://ld.dock.io/security/bddt16/v1",
{
"dk": "https://ld.dock.io/credentials#",
"name": "dk:name"
}
],
"id": "https://***REMOVED***/f20a0b87efb4ccd4313b2d08056e48e12a1927d747d68bb2d45c722d9cd8e6be",
"type": [
"VerifiableCredential",
"BasicCredential"
],
"credentialSubject": {
"id": "did:key:z6Mkujh84aPZ3EaYP6t7ftS2kLRkw6G7pkKswEJq2bn86ALs",
"name": "T VPI"
},
"issuanceDate": "2024-05-03T06:50:36.901Z",
"issuer": {
"name": "Profile bbs",
"id": "did:dock:5CDpZeS2fAas4Du87f2VyTj1BospmvQ5BZwZrKFtb3GeAppq"
},
"credentialSchema": {
"id": "data:application/json;charset=utf-8,%7B%22%24id%22%3A%22https%3A%2F%2Fschema.dock.io%2FBasicCredential-V2-1703777584571.json%22%2C%22%24schema%22%3A%22http%3A%2F%2Fjson-schema.org%2Fdraft-07%2Fschema%23%22%2C%22additionalProperties%22%3Atrue%2C%22description%22%3A%22A%20representation%20of%20a%20very%20basic%20example%20credential%22%2C%22name%22%3A%22Basic%20Credential%22%2C%22properties%22%3A%7B%22%40context%22%3A%7B%22type%22%3A%22string%22%7D%2C%22credentialSchema%22%3A%7B%22type%22%3A%22string%22%7D%2C%22credentialSubject%22%3A%7B%22properties%22%3A%7B%22id%22%3A%7B%22description%22%3A%22A%20unique%20identifier%20of%20the%20recipient.%20Example%3A%20DID%2C%20email%20address%2C%20national%20ID%20number%2C%20employee%20ID%2C%20student%20ID%20etc.%20If%20you%20enter%20the%20recipient's%20DID%2C%20the%20person%20will%20automatically%20receive%20the%20credential%20in%20their%20Dock%20wallet.%22%2C%22title%22%3A%22Subject%20ID%22%2C%22type%22%3A%22string%22%7D%2C%22name%22%3A%7B%22description%22%3A%22The%20name%20of%20the%20credential%20holder.%22%2C%22title%22%3A%22Subject%20Name%22%2C%22type%22%3A%22string%22%7D%7D%2C%22required%22%3A%5B%22name%22%5D%2C%22type%22%3A%22object%22%7D%2C%22cryptoVersion%22%3A%7B%22type%22%3A%22string%22%7D%2C%22id%22%3A%7B%22type%22%3A%22string%22%7D%2C%22issuanceDate%22%3A%7B%22format%22%3A%22date-time%22%2C%22type%22%3A%22string%22%7D%2C%22issuer%22%3A%7B%22properties%22%3A%7B%22id%22%3A%7B%22type%22%3A%22string%22%7D%2C%22name%22%3A%7B%22type%22%3A%22string%22%7D%7D%2C%22type%22%3A%22object%22%7D%2C%22name%22%3A%7B%22type%22%3A%22string%22%7D%2C%22proof%22%3A%7B%22properties%22%3A%7B%22%40context%22%3A%7B%22items%22%3A%5B%7B%22properties%22%3A%7B%22proof%22%3A%7B%22properties%22%3A%7B%22%40container%22%3A%7B%22type%22%3A%22string%22%7D%2C%22%40id%22%3A%7B%22type%22%3A%22string%22%7D%2C%22%40type%22%3A%7B%22type%22%3A%22string%22%7D%7D%2C%22type%22%3A%22object%22%7D%2C%22sec%22%3A%7B%22type%22%3A%22string%22%7D%7D%2C%22type%22%3A%22object%22%7D%2C%7B%22type%22%3A%22string%22%7D%5D%2C%22type%22%3A%22array%22%7D%2C%22created%22%3A%7B%22format%22%3A%22date-time%22%2C%22type%22%3A%22string%22%7D%2C%22proofPurpose%22%3A%7B%22type%22%3A%22string%22%7D%2C%22type%22%3A%7B%22type%22%3A%22string%22%7D%2C%22verificationMethod%22%3A%7B%22type%22%3A%22string%22%7D%7D%2C%22type%22%3A%22object%22%7D%2C%22type%22%3A%7B%22type%22%3A%22string%22%7D%7D%2C%22type%22%3A%22object%22%7D",
"type": "JsonSchemaValidator2018",
"parsingOptions": {
"useDefaults": true,
"defaultMinimumInteger": -4294967295,
"defaultMinimumDate": -17592186044415,
"defaultDecimalPlaces": 4
},
"version": "0.3.0"
},
"name": "VPI credential",
"cryptoVersion": "0.5.0",
"proof": {
"@context": [
{
"sec": "https://w3id.org/security#",
"proof": {
"@id": "sec:proof",
"@type": "@id",
"@container": "@graph"
}
},
"https://ld.dock.io/security/bddt16/v1"
],
"type": "Bls12381BDDT16MACDock2024",
"created": "2024-05-03T06:50:37Z",
"verificationMethod": "did:dock:5CDpZeS2fAas4Du87f2VyTj1BospmvQ5BZwZrKFtb3GeAppq#7jvC9bs4PXheGvBsbRUQQqXCXddRBK1fNmZfJfTnM7HW6jioKVBZN35tFLyG2yTte9",
"proofPurpose": "assertionMethod",
"proofValue": "zb1UmaJsbUqDCLDYdmhR34kFD1YdcwaJc3YbN3kXJKn7g6zpZwVCKUufKtnaJJ8AthESCz2txWBMm7MsjoEsFEEz1kpWVBTsPQrYPm3hU7o4MA1KT1MkwLmqy5VCbaLbSdBoXZWaAzkkbZmd2453XS9iBV"
}
}

describe('VPI verification', () => {
it('should verify a vpi credential', async () => {
const wallet: IWallet = await getWallet();

getCredentialProvider().addCredential(credential);

const proofRequest = await createProofRequest(
ProofTemplateIds.ANY_CREDENTIAL,
);

const result: any = await getCredentialProvider().isValid(credential);

expect(result.status).toBe('verified');

const controller = await createVerificationController({
wallet,
});

await controller.start({
template: proofRequest,
});

let attributesToReveal = ['credentialSubject.name'];

controller.selectedCredentials.set(credential.id, {
credential: credential,
attributesToReveal,
});

const presentation = await controller.createPresentation();
console.log('Presentation generated');
console.log(JSON.stringify(presentation, null, 2));
console.log('Sending presentation to Certs API');

let certsResponse;
try {
certsResponse = await controller.submitPresentation(presentation);
console.log('CERTS response');
console.log(JSON.stringify(certsResponse, null, 2));
} catch (err) {
certsResponse = err.response.data;
console.log('Certs API returned an error');
console.log(JSON.stringify(certsResponse, null, 2));
}

expect(certsResponse.verified).toBe(true);
});

afterAll(() => closeWallet());
});
15 changes: 10 additions & 5 deletions packages/core/src/verification-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ export function createVerificationController({
return credentialServiceRPC.isBBSPlusCredential({credential});
}

async function isBDDTCredential(credential) {
return credentialServiceRPC.isBDDTCredential({credential});
}

async function createPresentation() {
assert(!!selectedDID, 'No DID selected');
assert(!!selectedCredentials.size, 'No credentials selected');
Expand All @@ -164,13 +168,12 @@ export function createVerificationController({

for (const credentialSelection of selectedCredentials.values()) {
const isBBS = await isBBSPlusCredential(credentialSelection.credential);
const isBDDT = await isBDDTCredential(credentialSelection.credential);

if (!isBBS) {
credentials.push(credentialSelection.credential);
} else {
// derive BBS credential
if (isBBS || isBDDT) {
// derive credential
const derivedCredentials =
await credentialServiceRPC.deriveVCFromBBSPresentation({
await credentialServiceRPC.deriveVCFromPresentation({
proofRequest: templateJSON,

credentials: [
Expand All @@ -188,6 +191,8 @@ export function createVerificationController({
console.log('Credential derived');

credentials.push(derivedCredentials[0]);
} else {
credentials.push(credentialSelection.credential);
}
}

Expand Down
146 changes: 146 additions & 0 deletions packages/react-native/lib/documentsHooks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { useDocument, useDocuments } from './documentsHooks';
import { WalletEvents } from '@docknetwork/wallet-sdk-wasm/src/modules/wallet';
import { getWallet } from './wallet';
import { act, renderHook } from '@testing-library/react-hooks';

const mockDocument = { id: 'mock-document-id' };

const mockWallet = {
getDocumentById: jest.fn(() => Promise.resolve(mockDocument)),
getDocumentsByType: jest.fn(() => Promise.resolve([])),
eventManager: {
on: jest.fn(),
removeListener: jest.fn(),
},
};

jest.mock('./wallet', () => ({
getWallet: jest.fn(() => mockWallet),
}));

describe('useDocument', () => {

beforeEach(() => {
getWallet.mockReturnValue(mockWallet);
mockWallet.getDocumentById.mockReset();
mockWallet.eventManager.on.mockReset();
mockWallet.eventManager.removeListener.mockReset();
});

it('should fetch the document by documentId', async () => {
const documentId = 'document-id';
const document = { id: documentId, content: 'content' };
mockWallet.getDocumentById.mockResolvedValue(document);

const { result, waitFor } = renderHook(() =>
useDocument(documentId),
);

await waitFor(() => expect(getWallet).toHaveBeenCalled())
expect(mockWallet.getDocumentById).toHaveBeenCalledWith(documentId);
expect(result.current).toEqual(document);
});

it('should fetch the document when documentUpdated event is emitted', async () => {
const documentId = 'document-id';
const initialDocument = { id: documentId, content: 'content' };
const updatedDocument = { id: documentId, content: 'updated content' };
mockWallet.getDocumentById
.mockResolvedValueOnce(initialDocument)
.mockResolvedValueOnce(updatedDocument);

const { result, waitFor } = renderHook(() =>
useDocument(documentId),
);

act(() => {
mockWallet.eventManager.on.mock.calls[0][1](updatedDocument);
});

await waitFor(() => expect(mockWallet.getDocumentById).toHaveBeenCalledTimes(2));
expect(result.current).toEqual(updatedDocument);
});

it('should fetch the document when documentAdded event is emitted', async () => {
const documentId = 'document-id';
const initialDocument = null;
const newDocument = { id: documentId, content: 'added content' };
mockWallet.getDocumentById
.mockResolvedValueOnce(initialDocument)
.mockResolvedValueOnce(newDocument);

const { result, waitFor } = renderHook(() =>
useDocument(documentId),
);

act(() => {
mockWallet.eventManager.on.mock.calls[0][1](newDocument);
});

await waitFor(() => expect(mockWallet.getDocumentById).toHaveBeenCalledTimes(2));
expect(result.current).toEqual(newDocument);
});

it('should fetch the document when documentRemoved event is emitted', async () => {
const documentId = 'document-id';
const initialDocument = { id: documentId, content: 'content' };
const newDocument = { id: documentId, content: 'updated content' };
mockWallet.getDocumentById
.mockResolvedValueOnce(initialDocument)
.mockResolvedValueOnce(newDocument);

const { result, waitFor } = renderHook(() =>
useDocument(documentId),
);

act(() => {
mockWallet.eventManager.on.mock.calls[0][1](newDocument);
});

await waitFor(() => expect(mockWallet.getDocumentById).toHaveBeenCalledTimes(2));
expect(result.current).toEqual(newDocument);
});

});

describe('useDocuments', () => {
beforeEach(() => {
getWallet.mockReturnValue(mockWallet);
mockWallet.getDocumentById.mockReset();
mockWallet.eventManager.on.mockReset();
mockWallet.eventManager.removeListener.mockReset();
});

it('should fetch the document correctly', async () => {
const type = 'type1';
const documents = [
{ id: 'doc1', type },
{ id: 'doc2', type },
];
mockWallet.getDocumentsByType.mockResolvedValue(documents);

const { result, waitFor } = renderHook(() => useDocuments({ type }));

await waitFor(() => expect(mockWallet.getDocumentsByType).toHaveBeenCalledWith(type));
expect(result.current.documents).toEqual(documents);
expect(result.current.loading).toEqual(false);
});
it('should refetch documents on networkUpdated event', async () => {
const { waitFor } = renderHook(() =>
useDocuments({ type: 'mockType' }),
);
await mockWallet.eventManager.on.mock.calls[2][1]();

expect(mockWallet.getDocumentsByType).toHaveBeenCalledWith('mockType');
});

it('should force refetch documents on networkUpdated event if type is not set', async () => {
const { waitFor } = renderHook(() =>
useDocuments(),
);
mockWallet.getDocumentsByType.mockReset();
await mockWallet.eventManager.on.mock.calls[3][1]();

expect(mockWallet.getDocumentsByType).toHaveBeenCalledTimes(1);
});
});
77 changes: 77 additions & 0 deletions packages/react-native/lib/documentsHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {WalletEvents} from '@docknetwork/wallet-sdk-wasm/src/modules/wallet';
import {useCallback, useEffect, useState} from 'react';
import {getWallet} from './wallet';

const useEventListener = (eventManager, eventNames, listener) => {
useEffect(() => {
eventNames.forEach(eventName => eventManager.on(eventName, listener));
return () =>
eventNames.forEach(eventName =>
eventManager.removeListener(eventName, listener),
);
}, [eventManager, eventNames, listener]);
};

const events = [
WalletEvents.documentAdded,
WalletEvents.documentRemoved,
WalletEvents.documentUpdated,
];

export function useDocument(id) {
const [document, setDocument] = useState(null);

const refetchDocument = useCallback(
async updatedDoc => {
if (updatedDoc.id !== id) return;
const doc = await getWallet().getDocumentById(id);
setDocument(doc);
},
[id],
);

useEffect(() => {
getWallet().getDocumentById(id).then(setDocument);
}, [id]);

useEventListener(getWallet().eventManager, events, refetchDocument);

return document;
}

export function useDocuments({type = null} = {}) {
const [documents, setDocuments] = useState([]);
const [loading, setLoading] = useState(true);

const fetchDocuments = useCallback(
async (updatedDoc, forceFetch = false) => {
console.log('fetching documents', updatedDoc, forceFetch);
if (
forceFetch ||
updatedDoc?.type === type ||
updatedDoc?.type?.includes(type)
) {
const docs = await getWallet().getDocumentsByType(type);
setDocuments(docs);
setLoading(false);
}
},
[type],
);

useEffect(() => {
fetchDocuments(null, true);
}, [fetchDocuments, setLoading]);

useEventListener(getWallet().eventManager, events, fetchDocuments);
useEventListener(
getWallet().eventManager,
[WalletEvents.networkUpdated],
async () => fetchDocuments(null, true),
);

return {
documents,
loading,
};
}
Loading

0 comments on commit a228021

Please sign in to comment.