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

Feature/onchain non merklized credential converter #290

Merged
merged 32 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e668d12
v0.0.1 adapter for non-merklized onchain issuer
ilya-korotya Nov 12, 2024
763a9c6
Merge branch 'main' into feature/onchain-non-merklized-credential-con…
ilya-korotya Nov 12, 2024
117742a
fix prettier
ilya-korotya Nov 12, 2024
fd10979
update package-lock
ilya-korotya Nov 12, 2024
4203f40
add ts-ignore for tests data
ilya-korotya Nov 12, 2024
d19e191
use internal import
ilya-korotya Nov 12, 2024
9da8763
use ipfs node url instead of gateway
ilya-korotya Nov 12, 2024
81ab215
add docs
ilya-korotya Nov 12, 2024
b68392c
fix prettier
ilya-korotya Nov 12, 2024
33ad9b9
move onchain-issuer package on storage level
ilya-korotya Nov 18, 2024
f8a433b
fix linter
ilya-korotya Nov 18, 2024
32592ac
use ipfs node url instead of gateway
ilya-korotya Nov 18, 2024
01dfd75
fix comments
ilya-korotya Nov 20, 2024
241c45c
use node instead of gateway
ilya-korotya Nov 20, 2024
d9f89a4
fix package.json
ilya-korotya Nov 20, 2024
6fe7eb2
fix json representation
ilya-korotya Nov 20, 2024
eb9e809
fix prettier
ilya-korotya Nov 20, 2024
9ab97d2
fix expiration time
ilya-korotya Nov 20, 2024
1b7ed93
avoid mutation
ilya-korotya Nov 21, 2024
c35fa83
Merge branch 'main' into feature/onchain-non-merklized-credential-con…
ilya-korotya Nov 21, 2024
aae225d
fix package-lock
ilya-korotya Nov 21, 2024
e0a18ff
use beta version for nock
ilya-korotya Nov 21, 2024
82c899c
fix comments
ilya-korotya Nov 22, 2024
2db0cb6
use unnecessary imports
ilya-korotya Nov 25, 2024
30c4882
handle onchain-offer message
ilya-korotya Nov 28, 2024
c7c5bb6
improvments
ilya-korotya Dec 2, 2024
6ca8c21
Merge branch 'main' into feature/onchain-non-merklized-credential-con…
ilya-korotya Dec 2, 2024
0170ffa
fix comments
ilya-korotya Dec 2, 2024
876e30d
fix comments
ilya-korotya Dec 3, 2024
e8abd2e
Merge branch 'main' into feature/onchain-non-merklized-credential-con…
ilya-korotya Dec 3, 2024
62d6094
rename options
ilya-korotya Dec 3, 2024
b7ab542
fix linter
ilya-korotya Dec 3, 2024
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
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
Loading