Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented storing encrypted credentials in Ceramic #15

Merged
merged 13 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/site/src/utils/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const createProof = async () => {
const data = await connector.createProof({
circuitId: 'credentialAtomicQueryMTPV2OnChain',
accountAddress: accounts[0],
issuerDid: 'did:iden3:tJgV5GSETVoEdg3BeQygWJdNEHHwZTSSiCB1NkM1u',
query: {
allowedIssuers: ['*'],
credentialSubject: {
Expand Down
5 changes: 5 additions & 0 deletions packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
"preversion": "yarn && yarn build && git add snap.manifest.json"
},
"dependencies": {
"@ceramicnetwork/http-client": "2.27.0",
"@ethersproject/abi": "5.0.0",
"@ethersproject/bytes": "5.7.0",
"@ethersproject/keccak256": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@glazed/did-datastore": "0.3.2",
"@iden3/js-crypto": "1.0.0-beta.1",
"@iden3/js-iden3-core": "1.0.0-beta.2",
"@iden3/js-jsonld-merklization": "1.0.0-beta.14",
Expand All @@ -47,8 +49,11 @@
"@metamask/snaps-ui": "0.32.2",
"@rarimo/rarime-connector": "0.8.0",
"buffer": "6.0.3",
"dids": "4.0.4",
"ethers": "5.7.2",
"intl": "1.2.5",
"key-did-provider-ed25519": "3.0.2",
"key-did-resolver": "3.0.0",
"typia": "4.1.3",
"uuid": "9.0.0"
},
Expand Down
5 changes: 5 additions & 0 deletions packages/snap/post-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ let bundleString = fs.readFileSync(bundlePath, 'utf8');

bundleString = 'var Worker = {};\n'.concat(bundleString);

bundleString = bundleString.replace(
"/** @type {import('cborg').TagDecoder[]} */",
'',
);

// Remove eval
bundleString = bundleString.replaceAll(`eval(`, 'evalIn(');

Expand Down
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/rarimo/rarime.git"
},
"source": {
"shasum": "y+9O7z7mlNe85T/RCp32jySBwGoJ9tf0NrKlLrpgNdw=",
"shasum": "hN3xHphCY/FFPt41YbZUy+ZRAHP6xUhjcJEjaCPdnOk=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
14 changes: 14 additions & 0 deletions packages/snap/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,17 @@ export const SUPPORTED_CHAINS: Record<number, ChainInfo> = {
};

export const GET_CREDENTIALS_SUPPORTED_HOSTNAMES = ['localhost'];

export const CERAMIC_ALIASES = {
definitions: {
encryptedCredentials:
'kjzl6cwe1jw149in7sud7u3s0cw4863vjos3rc2zickw6f091q9cg9n370e42xk',
},
schemas: {
EncryptedCredentials:
'ceramic://k3y52l7qbv1fry2o4dqxfgxjb8rcyw78vsv82o21pf5md2m2y252fm0ll68y3ddz4',
ihordiachenko marked this conversation as resolved.
Show resolved Hide resolved
},
tiles: {},
};

export const CERAMIC_URL = 'https://ceramic-clay.3boxlabs.com';
62 changes: 62 additions & 0 deletions packages/snap/src/helpers/ceramic-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DID as CeramicDID } from 'dids';
import { Ed25519Provider } from 'key-did-provider-ed25519';
import { getResolver } from 'key-did-resolver';
import { CeramicClient } from '@ceramicnetwork/http-client';
import { DIDDataStore } from '@glazed/did-datastore';
import { Hex } from '@iden3/js-crypto';
import { CERAMIC_ALIASES, CERAMIC_URL } from '../config';
import { getItemFromStore } from '../rpc';
import { StorageKeys } from '../enums';
import { W3CCredential } from '../types';

export const getCeramic = async () => {
const identityStorage = await getItemFromStore(StorageKeys.identity);
if (!identityStorage) {
throw new Error('Identity not created');
ihordiachenko marked this conversation as resolved.
Show resolved Hide resolved
}
const did = new CeramicDID({
provider: new Ed25519Provider(
Hex.decodeString(identityStorage.privateKeyHex),
),
resolver: getResolver(),
});
await did.authenticate();
const ceramic = new CeramicClient(CERAMIC_URL);
ceramic.setDID(did);
return ceramic;
};

export const getCeramicAndStore = async () => {
const ceramic = await getCeramic();
if (!ceramic.did) {
throw new Error('DID not setted');
ihordiachenko marked this conversation as resolved.
Show resolved Hide resolved
}
const datastore = new DIDDataStore({ ceramic, model: CERAMIC_ALIASES });
return { ceramic, datastore };
};

export const saveEncryptedCredentials = async (data: W3CCredential[]) => {
const { ceramic, datastore } = await getCeramicAndStore();

const jwe = await ceramic.did?.createJWE(
new TextEncoder().encode(JSON.stringify(data)),
[ceramic.did.id],
);
await datastore.merge('encryptedCredentials', {
data: btoa(JSON.stringify(jwe)),
});
};

export const getDecryptedCredentials = async (): Promise<W3CCredential[]> => {
const { ceramic, datastore } = await getCeramicAndStore();

const encryptedData = await datastore.get('encryptedCredentials');
if (!encryptedData) {
return [];
}
const data = await ceramic.did?.decryptJWE(
JSON.parse(atob(encryptedData.data)),
);

return JSON.parse(new TextDecoder().decode(data));
};
15 changes: 15 additions & 0 deletions packages/snap/src/helpers/common-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ export const getRarimoStateContractAddress = (chainId: number) => {
export const getHostname = (origin: string): string => {
return new URL(origin).hostname;
};

export const uniqBy = (arr: any[], predicate: string) => {
return [
...arr
.reduce((map, item) => {
const key =
item === null || item === undefined ? item : item[predicate];

map.has(key) || map.set(key, item);

return map;
}, new Map())
.values(),
];
};
37 changes: 11 additions & 26 deletions packages/snap/src/helpers/credential-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Claim } from '@iden3/js-iden3-core';
import { ProofType, StorageKeys } from '../enums';
import { getItemFromStore, setItemInStore } from '../rpc';
import { ProofType } from '../enums';
import {
CredentialStatus,
ProofQuery,
Expand All @@ -9,40 +8,26 @@ import {
} from '../types';
import { StandardJSONCredentialsQueryFilter } from './json-query-helpers';
import { getCoreClaimFromProof } from './proof-helpers';

export const saveCredential = async (
key: string,
value: W3CCredential,
keyName = 'id' as keyof W3CCredential,
): Promise<void> => {
const data = (await getItemFromStore(
StorageKeys.credentials,
)) as W3CCredential[];
const items = data || [];

const itemIndex = items.findIndex((i) => i[keyName] === key);
if (itemIndex === -1) {
items.push(value);
} else {
items[itemIndex] = value;
}
await setItemInStore(StorageKeys.credentials, items);
};
import {
getDecryptedCredentials,
saveEncryptedCredentials,
} from './ceramic-helpers';
import { uniqBy } from './common-helpers';

export const saveCredentials = async (
value: W3CCredential[],
credentials: W3CCredential[],
keyName = 'id' as keyof W3CCredential,
): Promise<void> => {
for (const credential of value) {
await saveCredential(credential.id, credential, keyName);
}
const data = await getDecryptedCredentials();
const items = uniqBy([...credentials, ...data], keyName);
await saveEncryptedCredentials(items);
};

export const findCredentialsByQuery = async (
query: ProofQuery,
): Promise<W3CCredential[]> => {
const filters = StandardJSONCredentialsQueryFilter(query);
const credentials = (await getItemFromStore(StorageKeys.credentials)) || [];
const credentials = await getDecryptedCredentials();
return credentials.filter((credential: W3CCredential) =>
filters.every((filter) => filter.execute(credential)),
);
Expand Down
3 changes: 2 additions & 1 deletion packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
isValidSaveCredentialsOfferRequest,
} from './typia-generated';
import { GET_CREDENTIALS_SUPPORTED_HOSTNAMES } from './config';
import { getDecryptedCredentials } from './helpers/ceramic-helpers';

export const onRpcRequest: OnRpcRequestHandler = async ({
request,
Expand Down Expand Up @@ -327,7 +328,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
if (!GET_CREDENTIALS_SUPPORTED_HOSTNAMES.includes(getHostname(origin))) {
throw new Error('This origin does not have access to credentials');
}
return (await getItemFromStore(StorageKeys.credentials)) || [];
return await getDecryptedCredentials();
}

default:
Expand Down
10 changes: 10 additions & 0 deletions patches/@ceramicnetwork+common+2.37.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
diff --git a/node_modules/@ceramicnetwork/common/lib/utils/fetch-json.js b/node_modules/@ceramicnetwork/common/lib/utils/fetch-json.js
index b3f4d3f..e3ac5b8 100644
--- a/node_modules/@ceramicnetwork/common/lib/utils/fetch-json.js
+++ b/node_modules/@ceramicnetwork/common/lib/utils/fetch-json.js
@@ -1,4 +1,4 @@
-import fetch from 'cross-fetch';
+import {fetch} from 'cross-fetch';
import { mergeAbortSignals, TimedAbortSignal, abortable } from './abort-signal-utils.js';
const DEFAULT_FETCH_TIMEOUT = 60 * 1000 * 3;
export async function fetchJson(url, opts = {}) {
38 changes: 38 additions & 0 deletions patches/cross-fetch+3.1.8.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
diff --git a/node_modules/cross-fetch/CHANGELOG.md b/node_modules/cross-fetch/CHANGELOG.md
deleted file mode 100644
index 68441b59a7e15e655df99c3160b8cd0e09d9d990..0000000000000000000000000000000000000000
diff --git a/node_modules/cross-fetch/dist/browser-ponyfill.js b/node_modules/cross-fetch/dist/browser-ponyfill.js
index f216aa35a9d32f86df5701c78cf7b039bc2cd47c..a5f764e94a12b2848dd1938b00117a8fe20614f8 100644
--- a/node_modules/cross-fetch/dist/browser-ponyfill.js
+++ b/node_modules/cross-fetch/dist/browser-ponyfill.js
@@ -543,12 +543,22 @@ __self__.fetch.ponyfill = true;
// Remove "polyfill" property added by whatwg-fetch
delete __self__.fetch.polyfill;
// Choose between native implementation (global) or custom implementation (__self__)
-// var ctx = global.fetch ? global : __self__;
-var ctx = __self__; // this line disable service worker support temporarily
-exports = ctx.fetch // To enable: import fetch from 'cross-fetch'
-exports.default = ctx.fetch // For TypeScript consumers without esModuleInterop.
-exports.fetch = ctx.fetch // To enable: import {fetch} from 'cross-fetch'
-exports.Headers = ctx.Headers
-exports.Request = ctx.Request
-exports.Response = ctx.Response
+var ctx = global.fetch ? global : __self__;
+// var ctx = __self__; // this line disable service worker support temporarily
+// exports = ctx.fetch // To enable: import fetch from 'cross-fetch'
+// exports.default = ctx.fetch // For TypeScript consumers without esModuleInterop.
+// exports.fetch = ctx.fetch // To enable: import {fetch} from 'cross-fetch'
+// exports.Headers = ctx.Headers
+// exports.Request = ctx.Request
+// exports.Response = ctx.Response
+
+exports = {
+ ...ctx.fetch,
+ default: ctx.fetch,
+ fetch: ctx.fetch,
+ Headers: ctx.Headers,
+ Request: ctx.Request,
+ Response: ctx.Response,
+};
+
module.exports = exports
Loading