Skip to content

Commit

Permalink
add CredentialRefreshService & refreshService field
Browse files Browse the repository at this point in the history
  • Loading branch information
volodymyr-basiuk committed Jan 11, 2024
1 parent d9aabff commit f0edfc2
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/credentials/credential-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { IssuerResolver } from './status/sparse-merkle-tree';
import { AgentResolver } from './status/agent-revocation';
import { CredentialStatusResolveOptions } from './status/resolver';
import { getUserDIDFromCredential } from './utils';
import { RefreshService } from '../verifiable/refresh-service';

// ErrAllClaimsRevoked all claims are revoked.
const ErrAllClaimsRevoked = 'all claims are revoked';
Expand Down Expand Up @@ -46,6 +47,10 @@ export interface CredentialRequest {
* expiration time
*/
expiration?: number;
/**
* refreshService
*/
refreshService?: RefreshService;
/**
* claim version
*/
Expand Down Expand Up @@ -327,6 +332,7 @@ export class CredentialWallet implements ICredentialWallet {
cr['@context'] = context;
cr.type = credentialType;
cr.expirationDate = expirationDate ? new Date(expirationDate * 1000).toISOString() : undefined;
cr.refreshService = request.refreshService;
cr.issuanceDate = new Date().toISOString();
cr.credentialSubject = credentialSubject;
cr.issuer = issuer.string();
Expand Down
2 changes: 2 additions & 0 deletions src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({
CREDENTIAL_OFFER_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/1.0/offer',
// CredentialIssuanceResponseMessageType is type for message with a credential issuance
CREDENTIAL_ISSUANCE_RESPONSE_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/1.0/issuance-response',
// CredentialRefreshMessageType is type for message with a credential issuance
CREDENTIAL_REFRESH_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/1.0/refresh',
// DeviceRegistrationRequestMessageType defines device registration request type of the communication protocol
DEVICE_REGISTRATION_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'devices/1.0/registration',
// MessageFetMessageFetchRequestMessageType defines message fetch request type of the communication protocol.
Expand Down
17 changes: 17 additions & 0 deletions src/iden3comm/types/protocol/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,20 @@ export type Schema = {
url: string;
type: string;
};

/** CredentialRefreshMessage represent Iden3message for credential refresh request */
export type CredentialRefreshMessage = {
id: string;
typ?: MediaType;
type: ProtocolMessage;
thid?: string;
body?: CredentialRefreshMessageBody;
from?: string;
to?: string;
};

/** CredentialRefreshMessageBody is msg body for refresh request */
export type CredentialRefreshMessageBody = {
id: string;
reason: string;
};
9 changes: 9 additions & 0 deletions src/verifiable/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,12 @@ export enum SubjectPosition {
// Value save subject in value part of claim.
Value = 'value'
}

/**
* RefreshServiceType type for refreshService
*
* @enum {number}
*/
export enum RefreshServiceType {
Iden3RefreshService2023 = 'Iden3RefreshService2023'
}
4 changes: 3 additions & 1 deletion src/verifiable/credential.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { BJJSignatureProof2021, Iden3SparseMerkleTreeProof, CredentialStatus } from './proof';
import { Claim } from '@iden3/js-iden3-core';
import { ProofType } from './constants';
import { ProofType, RefreshServiceType } from './constants';
import { Proof } from '@iden3/js-merkletree';
import { Merklizer, Options } from '@iden3/js-jsonld-merklization';
import { RefreshService } from './refresh-service';

/**
* W3C Verifiable credential
Expand All @@ -17,6 +18,7 @@ export class W3CCredential {
'@context': string[] = [];
type: string[] = [];
expirationDate?: string;
refreshService?: RefreshService;
issuanceDate?: string;
credentialSubject: { [key: string]: object | string | number | boolean } = {};
credentialStatus!: CredentialStatus;
Expand Down
126 changes: 126 additions & 0 deletions src/verifiable/refresh-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { DID, Claim } from '@iden3/js-iden3-core';
import { PackageManager, ZKPPacker } from '../iden3comm';
import { RefreshServiceType } from './constants';
import {
CredentialIssuanceMessage,
CredentialRefreshMessage,
ZKPPackerParams
} from '../../src/iden3comm/types';
import { proving } from '@iden3/js-jwz';
import { CircuitId } from '../circuits';
import { randomUUID } from 'crypto';
import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../iden3comm/constants';
import { W3CCredential } from './credential';
import { byteEncoder } from '../utils';

/**
* RefreshService contains type and id
* @public
* @interface RefreshService
*/
export interface RefreshService {
id: string;
type: RefreshServiceType | string;
}

/**
* Interface to work with credential refresh service
*
* @public
* @interface IRefreshService
*/
export interface IRefreshService {
/**
* load circuit keys by id
*
* @param {RefreshService} refreshService - refreshService field from W3C credential
* @param {DID} userDID - user DIID
* @param {Claim} claim - claim to refresh
* @returns `{Promise<CircuitData>}`
*/
refresh(
refreshService: RefreshService,
userDID: DID,
credential: W3CCredential
): Promise<W3CCredential>;
}

/**
* RefreshServiceOptions contains options for CredentialRefreshService
* @public
* @interface RefreshServiceOptions
*/
export interface RefreshServiceOptions {
packageManager: PackageManager;
}

export class CredentialRefreshService implements IRefreshService {
private readonly _opts: RefreshServiceOptions;
constructor(options: RefreshServiceOptions) {
this._opts = options;
}

async refresh(
refreshService: RefreshService,
userDID: DID,
credential: W3CCredential
): Promise<W3CCredential> {
if (refreshService.type !== RefreshServiceType.Iden3RefreshService2023) {
throw new Error(`refresh service type ${refreshService.type} is not supported`);
}

const otherIdentifier = credential.credentialSubject.id;

if (userDID.id !== otherIdentifier) {
throw new Error(
`userDID id ${userDID.id} does not match claim other identifier ${otherIdentifier}`
);
}

const zkpParams: ZKPPackerParams = {
senderDID: userDID,
profileNonce: 0,
provingMethodAlg: {
alg: proving.provingMethodGroth16AuthV2Instance.methodAlg.alg,
circuitId: CircuitId.AuthV2
}
};

const refreshMsg: CredentialRefreshMessage = {
id: randomUUID(),
typ: MediaType.ZKPMessage,
type: PROTOCOL_MESSAGE_TYPE.CREDENTIAL_REFRESH_MESSAGE_TYPE,
thid: randomUUID(),
body: {
id: otherIdentifier,
reason: 'expired'
},
from: otherIdentifier,
to: credential.issuer
};

const msgBytes = byteEncoder.encode(JSON.stringify(refreshMsg));
const jwzToken = await this._opts.packageManager.pack(
MediaType.ZKPMessage,
msgBytes,
zkpParams
);
const resp = await fetch(refreshService.id, {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: jwzToken
});

if (resp.status !== 200) {
throw new Error(`could not refresh W3C credential, return status ${resp.status}`);
}
const credIssuanceMsg = resp as unknown as CredentialIssuanceMessage;
if (!credIssuanceMsg.body?.credential) {
throw new Error('no credential in CredentialIssuanceMessage response');
}

return credIssuanceMsg.body.credential;
}
}

0 comments on commit f0edfc2

Please sign in to comment.