Skip to content

Commit

Permalink
Feature/onchain non merklized credential converter (#290)
Browse files Browse the repository at this point in the history
support onchain issuer
  • Loading branch information
ilya-korotya authored Dec 4, 2024
1 parent 1b17ebf commit dad41d7
Show file tree
Hide file tree
Showing 14 changed files with 1,057 additions and 414 deletions.
486 changes: 167 additions & 319 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"mocha": "10.2.0",
"nock": "^14.0.0-beta.15",
"nock": "^14.0.0-beta.16",
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"rollup": "^4.14.3",
Expand All @@ -98,6 +98,7 @@
"snarkjs": "0.7.4"
},
"dependencies": {
"@iden3/onchain-non-merklized-issuer-base-abi": "^0.0.3",
"@noble/curves": "^1.4.0",
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
Expand Down
102 changes: 12 additions & 90 deletions src/credentials/credential-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DID, getChainId } from '@iden3/js-iden3-core';
import { DID } from '@iden3/js-iden3-core';
import { IDataStorage } from '../storage/interfaces';
import {
W3CCredential,
Expand Down Expand Up @@ -266,101 +266,23 @@ export class CredentialWallet implements ICredentialWallet {
if (!schema.$metadata.uris['jsonLdContext']) {
throw new Error('jsonLdContext is missing is the schema');
}
request.context = request.context ?? [];
// do copy of request to avoid mutation
const r = { ...request };
r.context = r.context ?? [];
if (
request.displayMethod?.type === DisplayMethodType.Iden3BasicDisplayMethodV1 &&
!request.context.includes(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD)
r.displayMethod?.type === DisplayMethodType.Iden3BasicDisplayMethodV1 &&
!r.context.includes(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD)
) {
request.context.push(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD);
r.context.push(VerifiableConstants.JSONLD_SCHEMA.IDEN3_DISPLAY_METHOD);
}
const context = [
VerifiableConstants.JSONLD_SCHEMA.W3C_CREDENTIAL_2018,
...request.context,
VerifiableConstants.JSONLD_SCHEMA.IDEN3_CREDENTIAL,
schema.$metadata.uris['jsonLdContext']
];
r.context.push(schema.$metadata.uris['jsonLdContext']);
r.expiration = r.expiration ? r.expiration * 1000 : undefined;
r.id = r.id ? r.id : `urn:${uuid.v4()}`;
r.issuanceDate = r.issuanceDate ? r.issuanceDate * 1000 : Date.now();

const credentialType = [
VerifiableConstants.CREDENTIAL_TYPE.W3C_VERIFIABLE_CREDENTIAL,
request.type
];

const expirationDate =
!request.expiration || request.expiration == 0 ? null : request.expiration;

const credentialSubject = request.credentialSubject;
credentialSubject['type'] = request.type;

const cr = new W3CCredential();
cr.id = `urn:${uuid.v4()}`;
cr['@context'] = context;
cr.type = credentialType;
cr.expirationDate = expirationDate ? new Date(expirationDate * 1000).toISOString() : undefined;
cr.refreshService = request.refreshService;
cr.displayMethod = request.displayMethod;
cr.issuanceDate = new Date().toISOString();
cr.credentialSubject = credentialSubject;
cr.issuer = issuer.string();
cr.credentialSchema = {
id: request.credentialSchema,
type: VerifiableConstants.JSON_SCHEMA_VALIDATOR
};

cr.credentialStatus = this.buildCredentialStatus(request, issuer);

return cr;
return W3CCredential.fromCredentialRequest(issuer, r);
};

/**
* Builds credential status
* @param {CredentialRequest} request
* @returns `CredentialStatus`
*/
private buildCredentialStatus(request: CredentialRequest, issuer: DID): CredentialStatus {
const credentialStatus: CredentialStatus = {
id: request.revocationOpts.id,
type: request.revocationOpts.type,
revocationNonce: request.revocationOpts.nonce
};

switch (request.revocationOpts.type) {
case CredentialStatusType.SparseMerkleTreeProof:
return {
...credentialStatus,
id: `${credentialStatus.id.replace(/\/$/, '')}/${credentialStatus.revocationNonce}`
};
case CredentialStatusType.Iden3ReverseSparseMerkleTreeProof:
return {
...credentialStatus,
id: request.revocationOpts.issuerState
? `${credentialStatus.id.replace(/\/$/, '')}/node?state=${
request.revocationOpts.issuerState
}`
: `${credentialStatus.id.replace(/\/$/, '')}`
};
case CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023: {
const issuerId = DID.idFromDID(issuer);
const chainId = getChainId(DID.blockchainFromId(issuerId), DID.networkIdFromId(issuerId));
const searchParams = [
['revocationNonce', request.revocationOpts.nonce?.toString() || ''],
['contractAddress', `${chainId}:${request.revocationOpts.id}`],
['state', request.revocationOpts.issuerState || '']
]
.filter(([, value]) => Boolean(value))
.map(([key, value]) => `${key}=${value}`)
.join('&');

return {
...credentialStatus,
// `[did]:[methodid]:[chain]:[network]:[id]/credentialStatus?(revocationNonce=value)&[contractAddress=[chainID]:[contractAddress]]&(state=issuerState)`
id: `${issuer.string()}/credentialStatus?${searchParams}`
};
}
default:
return credentialStatus;
}
}

/**
* {@inheritDoc ICredentialWallet.findById}
*/
Expand Down
8 changes: 8 additions & 0 deletions src/credentials/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export type PublishMode = 'sync' | 'async' | 'callback';
* @interface CredentialRequest
*/
export interface CredentialRequest {
/**
* Credential ID
*/
id?: string;
/**
* JSON credential schema
*/
Expand Down Expand Up @@ -64,6 +68,10 @@ export interface CredentialRequest {
* merklizedRootPosition (index / value / none)
*/
merklizedRootPosition?: MerklizedRootPosition;
/**
* issuance Date
*/
issuanceDate?: number;

/**
* Revocation options
Expand Down
49 changes: 49 additions & 0 deletions src/iden3comm/handlers/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CredentialFetchRequestMessage,
CredentialIssuanceMessage,
CredentialsOfferMessage,
CredentialsOnchainOfferMessage,
IPackageManager,
JWSPackerParams,
MessageFetchRequestMessage
Expand All @@ -24,6 +25,7 @@ import {
IProtocolMessageHandler
} from './message-handler';
import { verifyExpiresTime } from './common';
import { IOnchainIssuer } from '../../storage';

/**
*
Expand Down Expand Up @@ -128,6 +130,7 @@ export class FetchHandler
private readonly _packerMgr: IPackageManager,
private readonly opts?: {
credentialWallet: ICredentialWallet;
onchainIssuer?: IOnchainIssuer;
}
) {
super();
Expand All @@ -152,11 +155,43 @@ export class FetchHandler
return this.handleFetchRequest(message as CredentialFetchRequestMessage);
case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ISSUANCE_RESPONSE_MESSAGE_TYPE:
return this.handleIssuanceResponseMsg(message as CredentialIssuanceMessage);
case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE: {
const result = await this.handleOnchainOfferMessage(
message as CredentialsOnchainOfferMessage
);
if (Array.isArray(result)) {
const credWallet = this.opts?.credentialWallet;
if (!credWallet) throw new Error('Credential wallet is not provided');
await credWallet.saveAll(result);
return null;
}
return result as BasicMessage;
}
default:
return super.handle(message, ctx);
}
}

private async handleOnchainOfferMessage(
offerMessage: CredentialsOnchainOfferMessage
): Promise<W3CCredential[]> {
if (!this.opts?.onchainIssuer) {
throw new Error('onchain issuer is not provided');
}
const credentials: W3CCredential[] = [];
for (const credentialInfo of offerMessage.body.credentials) {
const issuerDID = DID.parse(offerMessage.from);
const userDID = DID.parse(offerMessage.to);
const credential = await this.opts.onchainIssuer.getCredential(
issuerDID,
userDID,
BigInt(credentialInfo.id)
);
credentials.push(credential);
}
return credentials;
}

private async handleOfferMessage(
offerMessage: CredentialsOfferMessage,
ctx: {
Expand Down Expand Up @@ -275,6 +310,20 @@ export class FetchHandler
throw new Error('invalid protocol message response');
}

/**
* Handles only messages with credentials/1.0/onchain-offer type
* @beta
*/
async handleOnchainOffer(offer: Uint8Array): Promise<W3CCredential[]> {
const offerMessage = await FetchHandler.unpackMessage<CredentialsOnchainOfferMessage>(
this._packerMgr,
offer,
PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE
);

return this.handleOnchainOfferMessage(offerMessage);
}

private async handleFetchRequest(
msgRequest: CredentialFetchRequestMessage
): Promise<CredentialIssuanceMessage> {
Expand Down
1 change: 1 addition & 0 deletions src/storage/blockchain/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './state';
export * from './onchain-zkp-verifier';
export * from './onchain-revocation';
export * from './onchain-issuer';
export * from './did-resolver-readonly-storage';
export * from './erc20-helper';
Loading

0 comments on commit dad41d7

Please sign in to comment.