Skip to content

Commit dad41d7

Browse files
authored
Feature/onchain non merklized credential converter (#290)
support onchain issuer
1 parent 1b17ebf commit dad41d7

File tree

14 files changed

+1057
-414
lines changed

14 files changed

+1057
-414
lines changed

package-lock.json

+167-319
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
"eslint-config-prettier": "^8.8.0",
8080
"eslint-plugin-prettier": "^4.2.1",
8181
"mocha": "10.2.0",
82-
"nock": "^14.0.0-beta.15",
82+
"nock": "^14.0.0-beta.16",
8383
"prettier": "^2.7.1",
8484
"rimraf": "^5.0.5",
8585
"rollup": "^4.14.3",
@@ -98,6 +98,7 @@
9898
"snarkjs": "0.7.4"
9999
},
100100
"dependencies": {
101+
"@iden3/onchain-non-merklized-issuer-base-abi": "^0.0.3",
101102
"@noble/curves": "^1.4.0",
102103
"ajv": "8.12.0",
103104
"ajv-formats": "2.1.1",

src/credentials/credential-wallet.ts

+12-90
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DID, getChainId } from '@iden3/js-iden3-core';
1+
import { DID } from '@iden3/js-iden3-core';
22
import { IDataStorage } from '../storage/interfaces';
33
import {
44
W3CCredential,
@@ -266,101 +266,23 @@ export class CredentialWallet implements ICredentialWallet {
266266
if (!schema.$metadata.uris['jsonLdContext']) {
267267
throw new Error('jsonLdContext is missing is the schema');
268268
}
269-
request.context = request.context ?? [];
269+
// do copy of request to avoid mutation
270+
const r = { ...request };
271+
r.context = r.context ?? [];
270272
if (
271-
request.displayMethod?.type === DisplayMethodType.Iden3BasicDisplayMethodV1 &&
272-
!request.context.includes(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD)
273+
r.displayMethod?.type === DisplayMethodType.Iden3BasicDisplayMethodV1 &&
274+
!r.context.includes(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD)
273275
) {
274-
request.context.push(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD);
276+
r.context.push(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD);
275277
}
276-
const context = [
277-
VerifiableConstants.JSONLD_SCHEMA.W3C_CREDENTIAL_2018,
278-
...request.context,
279-
VerifiableConstants.JSONLD_SCHEMA.IDEN3_CREDENTIAL,
280-
schema.$metadata.uris['jsonLdContext']
281-
];
278+
r.context.push(schema.$metadata.uris['jsonLdContext']);
279+
r.expiration = r.expiration ? r.expiration * 1000 : undefined;
280+
r.id = r.id ? r.id : `urn:${uuid.v4()}`;
281+
r.issuanceDate = r.issuanceDate ? r.issuanceDate * 1000 : Date.now();
282282

283-
const credentialType = [
284-
VerifiableConstants.CREDENTIAL_TYPE.W3C_VERIFIABLE_CREDENTIAL,
285-
request.type
286-
];
287-
288-
const expirationDate =
289-
!request.expiration || request.expiration == 0 ? null : request.expiration;
290-
291-
const credentialSubject = request.credentialSubject;
292-
credentialSubject['type'] = request.type;
293-
294-
const cr = new W3CCredential();
295-
cr.id = `urn:${uuid.v4()}`;
296-
cr['@context'] = context;
297-
cr.type = credentialType;
298-
cr.expirationDate = expirationDate ? new Date(expirationDate * 1000).toISOString() : undefined;
299-
cr.refreshService = request.refreshService;
300-
cr.displayMethod = request.displayMethod;
301-
cr.issuanceDate = new Date().toISOString();
302-
cr.credentialSubject = credentialSubject;
303-
cr.issuer = issuer.string();
304-
cr.credentialSchema = {
305-
id: request.credentialSchema,
306-
type: VerifiableConstants.JSON_SCHEMA_VALIDATOR
307-
};
308-
309-
cr.credentialStatus = this.buildCredentialStatus(request, issuer);
310-
311-
return cr;
283+
return W3CCredential.fromCredentialRequest(issuer, r);
312284
};
313285

314-
/**
315-
* Builds credential status
316-
* @param {CredentialRequest} request
317-
* @returns `CredentialStatus`
318-
*/
319-
private buildCredentialStatus(request: CredentialRequest, issuer: DID): CredentialStatus {
320-
const credentialStatus: CredentialStatus = {
321-
id: request.revocationOpts.id,
322-
type: request.revocationOpts.type,
323-
revocationNonce: request.revocationOpts.nonce
324-
};
325-
326-
switch (request.revocationOpts.type) {
327-
case CredentialStatusType.SparseMerkleTreeProof:
328-
return {
329-
...credentialStatus,
330-
id: `${credentialStatus.id.replace(/\/$/, '')}/${credentialStatus.revocationNonce}`
331-
};
332-
case CredentialStatusType.Iden3ReverseSparseMerkleTreeProof:
333-
return {
334-
...credentialStatus,
335-
id: request.revocationOpts.issuerState
336-
? `${credentialStatus.id.replace(/\/$/, '')}/node?state=${
337-
request.revocationOpts.issuerState
338-
}`
339-
: `${credentialStatus.id.replace(/\/$/, '')}`
340-
};
341-
case CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023: {
342-
const issuerId = DID.idFromDID(issuer);
343-
const chainId = getChainId(DID.blockchainFromId(issuerId), DID.networkIdFromId(issuerId));
344-
const searchParams = [
345-
['revocationNonce', request.revocationOpts.nonce?.toString() || ''],
346-
['contractAddress', `${chainId}:${request.revocationOpts.id}`],
347-
['state', request.revocationOpts.issuerState || '']
348-
]
349-
.filter(([, value]) => Boolean(value))
350-
.map(([key, value]) => `${key}=${value}`)
351-
.join('&');
352-
353-
return {
354-
...credentialStatus,
355-
// `[did]:[methodid]:[chain]:[network]:[id]/credentialStatus?(revocationNonce=value)&[contractAddress=[chainID]:[contractAddress]]&(state=issuerState)`
356-
id: `${issuer.string()}/credentialStatus?${searchParams}`
357-
};
358-
}
359-
default:
360-
return credentialStatus;
361-
}
362-
}
363-
364286
/**
365287
* {@inheritDoc ICredentialWallet.findById}
366288
*/

src/credentials/models.ts

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export type PublishMode = 'sync' | 'async' | 'callback';
2323
* @interface CredentialRequest
2424
*/
2525
export interface CredentialRequest {
26+
/**
27+
* Credential ID
28+
*/
29+
id?: string;
2630
/**
2731
* JSON credential schema
2832
*/
@@ -64,6 +68,10 @@ export interface CredentialRequest {
6468
* merklizedRootPosition (index / value / none)
6569
*/
6670
merklizedRootPosition?: MerklizedRootPosition;
71+
/**
72+
* issuance Date
73+
*/
74+
issuanceDate?: number;
6775

6876
/**
6977
* Revocation options

src/iden3comm/handlers/fetch.ts

+49
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CredentialFetchRequestMessage,
77
CredentialIssuanceMessage,
88
CredentialsOfferMessage,
9+
CredentialsOnchainOfferMessage,
910
IPackageManager,
1011
JWSPackerParams,
1112
MessageFetchRequestMessage
@@ -24,6 +25,7 @@ import {
2425
IProtocolMessageHandler
2526
} from './message-handler';
2627
import { verifyExpiresTime } from './common';
28+
import { IOnchainIssuer } from '../../storage';
2729

2830
/**
2931
*
@@ -128,6 +130,7 @@ export class FetchHandler
128130
private readonly _packerMgr: IPackageManager,
129131
private readonly opts?: {
130132
credentialWallet: ICredentialWallet;
133+
onchainIssuer?: IOnchainIssuer;
131134
}
132135
) {
133136
super();
@@ -152,11 +155,43 @@ export class FetchHandler
152155
return this.handleFetchRequest(message as CredentialFetchRequestMessage);
153156
case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ISSUANCE_RESPONSE_MESSAGE_TYPE:
154157
return this.handleIssuanceResponseMsg(message as CredentialIssuanceMessage);
158+
case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE: {
159+
const result = await this.handleOnchainOfferMessage(
160+
message as CredentialsOnchainOfferMessage
161+
);
162+
if (Array.isArray(result)) {
163+
const credWallet = this.opts?.credentialWallet;
164+
if (!credWallet) throw new Error('Credential wallet is not provided');
165+
await credWallet.saveAll(result);
166+
return null;
167+
}
168+
return result as BasicMessage;
169+
}
155170
default:
156171
return super.handle(message, ctx);
157172
}
158173
}
159174

175+
private async handleOnchainOfferMessage(
176+
offerMessage: CredentialsOnchainOfferMessage
177+
): Promise<W3CCredential[]> {
178+
if (!this.opts?.onchainIssuer) {
179+
throw new Error('onchain issuer is not provided');
180+
}
181+
const credentials: W3CCredential[] = [];
182+
for (const credentialInfo of offerMessage.body.credentials) {
183+
const issuerDID = DID.parse(offerMessage.from);
184+
const userDID = DID.parse(offerMessage.to);
185+
const credential = await this.opts.onchainIssuer.getCredential(
186+
issuerDID,
187+
userDID,
188+
BigInt(credentialInfo.id)
189+
);
190+
credentials.push(credential);
191+
}
192+
return credentials;
193+
}
194+
160195
private async handleOfferMessage(
161196
offerMessage: CredentialsOfferMessage,
162197
ctx: {
@@ -275,6 +310,20 @@ export class FetchHandler
275310
throw new Error('invalid protocol message response');
276311
}
277312

313+
/**
314+
* Handles only messages with credentials/1.0/onchain-offer type
315+
* @beta
316+
*/
317+
async handleOnchainOffer(offer: Uint8Array): Promise<W3CCredential[]> {
318+
const offerMessage = await FetchHandler.unpackMessage<CredentialsOnchainOfferMessage>(
319+
this._packerMgr,
320+
offer,
321+
PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE
322+
);
323+
324+
return this.handleOnchainOfferMessage(offerMessage);
325+
}
326+
278327
private async handleFetchRequest(
279328
msgRequest: CredentialFetchRequestMessage
280329
): Promise<CredentialIssuanceMessage> {

src/storage/blockchain/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './state';
22
export * from './onchain-zkp-verifier';
33
export * from './onchain-revocation';
4+
export * from './onchain-issuer';
45
export * from './did-resolver-readonly-storage';
56
export * from './erc20-helper';

0 commit comments

Comments
 (0)