Skip to content

Commit

Permalink
feat: Support all signature suites in jsonld [DEV-4346] (#587)
Browse files Browse the repository at this point in the history
* feat: Support all signature suites in jsonld

* Add tests

* Update jsonld proof type

* fix unit tests

* Make tests work in parallel

* Support proofValue or jws

* npm update

* Update package-lock.json

---------

Co-authored-by: Ankur Banerjee <[email protected]>
  • Loading branch information
DaevMithran and ankurdotb authored Sep 2, 2024
1 parent 2390b87 commit 60581f6
Show file tree
Hide file tree
Showing 11 changed files with 3,807 additions and 905 deletions.
4,441 changes: 3,555 additions & 886 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"@cheqd/ts-proto": "^3.4.4",
"@cosmjs/amino": "^0.32.4",
"@cosmjs/encoding": "^0.32.4",
"@logto/express": "^2.3.14",
"@logto/express": "^2.3.15",
"@stablelib/ed25519": "^1.0.3",
"@veramo/core": "^6.0.0",
"@veramo/credential-ld": "^6.0.0",
Expand Down Expand Up @@ -111,7 +111,7 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^10.1.6",
"@semantic-release/github": "^10.1.7",
"@semantic-release/npm": "^12.0.1",
"@semantic-release/release-notes-generator": "^14.0.1",
"@types/bcrypt": "^5.0.2",
Expand All @@ -124,12 +124,12 @@
"@types/helmet": "^4.0.0",
"@types/json-stringify-safe": "^5.0.3",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.16.1",
"@types/node": "^20.16.3",
"@types/secp256k1": "^4.0.6",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"@types/uuid": "^9.0.8",
"@types/validator": "^13.12.0",
"@types/validator": "^13.12.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"buffer": "6.0.3",
Expand All @@ -141,10 +141,10 @@
"prettier": "^3.3.3",
"semantic-release": "^24.1.0",
"swagger-jsdoc": "^6.2.8",
"ts-jest": "^29.2.4",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsx": "^4.17.0",
"tsx": "^4.19.0",
"typescript": "^5.5.4",
"uint8arrays": "^5.1.0"
},
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/validator/jsonld-proof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ export class JsonLDProofValidator implements IValidator {
error: 'Proof.verificationMethod is required',
};
}
if (!proof.jws) {
if (!proof.proofValue && !proof.jws) {
return {
valid: false,
error: 'Proof.jws is required',
error: 'Proof.proofValue or Proof.jws is required',
};
}
return { valid: true };
Expand Down
14 changes: 12 additions & 2 deletions src/services/identity/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ import { DIDStore, KeyStore } from '@veramo/data-store';
import { DIDManager } from '@veramo/did-manager';
import { DIDResolverPlugin, getUniversalResolver as UniversalResolver } from '@veramo/did-resolver';
import { CredentialPlugin } from '@veramo/credential-w3c';
import { CredentialIssuerLD, LdDefaultContexts, VeramoEd25519Signature2018 } from '@veramo/credential-ld';
import {
CredentialIssuerLD,
LdDefaultContexts,
VeramoEd25519Signature2018,
VeramoEd25519Signature2020,
VeramoJsonWebSignature2020,
} from '@veramo/credential-ld';
import {
Cheqd,
getResolver as CheqdDidResolver,
Expand Down Expand Up @@ -146,7 +152,11 @@ export class Veramo {
new CredentialPlugin(),
new CredentialIssuerLD({
contextMaps: [LdDefaultContexts],
suites: [new VeramoEd25519Signature2018()],
suites: [
new VeramoJsonWebSignature2020(),
new VeramoEd25519Signature2018(),
new VeramoEd25519Signature2020(),
],
})
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,6 @@ export enum OperationNameEnum {

export const JWT_PROOF_TYPE = 'JwtProof2020';
export const StatusList2021Entry = 'StatusList2021Entry';
export const JSONLD_PROOF_TYPES = ['Ed25519Signature2018', 'Ed25519Signature2020'];
export const JSONLD_PROOF_TYPES = ['Ed25519Signature2018', 'Ed25519Signature2020', 'JsonWebSignature2020'];
export const DEFAULT_PAGINATION_LIST_LIMIT = 10;
export const DefaultStudioRoleName = 'default' as const;
3 changes: 2 additions & 1 deletion src/types/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export type JSONLDProofType = {
created: string;
verificationMethod: string;
proofPurpose: string;
jws: string;
proofValue?: string;
jws?: string;
};
export type CheqdCredentialStatus = {
id: string;
Expand Down
204 changes: 204 additions & 0 deletions tests/e2e/parallel/credential/issue-verify-flow.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import type { VerifiableCredential } from '@veramo/core';

import { test, expect } from '@playwright/test';
import { StatusCodes } from 'http-status-codes';
import * as fs from 'fs';
import { CONTENT_TYPE, PAYLOADS_PATH } from '../../constants';

test.use({ storageState: 'playwright/.auth/user.json' });

test(' Issue a jwt credential', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jwt.json`, 'utf-8')
);
const issueResponse = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const jwtCredential: VerifiableCredential = await issueResponse.json();
expect(issueResponse).toBeOK();
expect(issueResponse.status()).toBe(StatusCodes.OK);
expect(jwtCredential.proof.type).toBe('JwtProof2020');
expect(jwtCredential.proof).toHaveProperty('jwt');
expect(typeof jwtCredential.issuer === 'string' ? jwtCredential.issuer : jwtCredential.issuer.id).toBe(
credentialData.issuerDid
);
expect(jwtCredential.type).toContain('VerifiableCredential');
expect(jwtCredential.credentialSubject).toMatchObject({
...credentialData.attributes,
id: credentialData.subjectDid,
});

const verifyResponse = await request.post(`/credential/verify`, {
data: JSON.stringify({
credential: jwtCredential.proof.jwt,
}),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const result = await verifyResponse.json();
expect(verifyResponse).toBeOK();
expect(verifyResponse.status()).toBe(StatusCodes.OK);
expect(result.verified).toBe(true);
});

test(' Issue a jwt credential with a deactivated DID', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jwt.json`, 'utf-8')
);
credentialData.issuerDid = 'did:cheqd:testnet:edce6dfb-b59c-493b-a4b8-1d16a6184349';
const response = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
expect(response.status()).toBe(StatusCodes.BAD_REQUEST);
});

test(' Issue a jsonLD credential with Ed25519VerificationKey2018', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jsonld-ed25519-2018.json`, 'utf-8')
);
const issueResponse = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': 'application/json',
},
});
const jsonldCredential: VerifiableCredential = await issueResponse.json();
expect(issueResponse).toBeOK();
expect(issueResponse.status()).toBe(StatusCodes.OK);
expect(jsonldCredential.proof.type).toBe('Ed25519Signature2018');
expect(jsonldCredential.proof).toHaveProperty('jws');
expect(typeof jsonldCredential.issuer === 'string' ? jsonldCredential.issuer : jsonldCredential.issuer.id).toBe(
credentialData.issuerDid
);
expect(jsonldCredential.type).toContain('VerifiableCredential');
expect(jsonldCredential.credentialSubject).toMatchObject({
...credentialData.attributes,
id: credentialData.subjectDid,
});

const verifyResponse = await request.post(`/credential/verify`, {
data: JSON.stringify({
credential: jsonldCredential,
fetchRemoteContexts: true,
}),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const result = await verifyResponse.json();
expect(verifyResponse).toBeOK();
expect(verifyResponse.status()).toBe(StatusCodes.OK);
expect(result.verified).toBe(true);
});

test(' Issue a jsonLD credential with Ed25519VerificationKey2020', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jsonld-ed25519-2020.json`, 'utf-8')
);
const issueResponse = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': 'application/json',
},
});
const jsonldCredential = await issueResponse.json();
expect(issueResponse).toBeOK();
expect(issueResponse.status()).toBe(StatusCodes.OK);
expect(jsonldCredential.proof.type).toBe('Ed25519Signature2020');
expect(jsonldCredential.proof).toHaveProperty('proofValue');
expect(typeof jsonldCredential.issuer === 'string' ? jsonldCredential.issuer : jsonldCredential.issuer.id).toBe(
credentialData.issuerDid
);
expect(jsonldCredential.type).toContain('VerifiableCredential');
expect(jsonldCredential.credentialSubject).toMatchObject({
...credentialData.attributes,
id: credentialData.subjectDid,
});
expect(jsonldCredential['@context']).toContain('https://w3id.org/security/suites/ed25519-2020/v1');

const verifyResponse = await request.post(`/credential/verify`, {
data: JSON.stringify({
credential: jsonldCredential,
fetchRemoteContexts: true,
}),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const result = await verifyResponse.json();
expect(verifyResponse).toBeOK();
expect(verifyResponse.status()).toBe(StatusCodes.OK);
expect(result.verified).toBe(true);
});

test(' Issue a jsonLD credential with JsonWebKey2020', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jsonld-jsonwebkey-2020.json`, 'utf-8')
);
const issueResponse = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': 'application/json',
},
});
const jsonldCredential = await issueResponse.json();
expect(issueResponse).toBeOK();
expect(issueResponse.status()).toBe(StatusCodes.OK);
expect(jsonldCredential.proof.type).toBe('JsonWebSignature2020');
expect(jsonldCredential.proof).toHaveProperty('jws');
expect(typeof jsonldCredential.issuer === 'string' ? jsonldCredential.issuer : jsonldCredential.issuer.id).toBe(
credentialData.issuerDid
);
expect(jsonldCredential.type).toContain('VerifiableCredential');
expect(jsonldCredential.credentialSubject).toMatchObject({
...credentialData.attributes,
id: credentialData.subjectDid,
});
expect(jsonldCredential['@context']).toContain('https://w3id.org/security/suites/jws-2020/v1');

const verifyResponse = await request.post(`/credential/verify`, {
data: JSON.stringify({
credential: jsonldCredential,
fetchRemoteContexts: true,
}),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const result = await verifyResponse.json();
expect(verifyResponse).toBeOK();
expect(verifyResponse.status()).toBe(StatusCodes.OK);
expect(result.verified).toBe(true);
});

test.skip(' Issue a jwt credential to a verida DID holder', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-vda.json`, 'utf-8')
);
const response = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const credential = await response.json();
expect(response).toBeOK();
expect(response.status()).toBe(StatusCodes.OK);
expect(credential.proof.type).toBe('JwtProof2020');
expect(credential.proof).toHaveProperty('jwt');
expect(typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id).toBe(
credentialData.issuerDid
);
expect(credential.type).toContain('VerifiableCredential');
expect(credential.credentialSubject).toMatchObject({
...credentialData.attributes,
id: credentialData.subjectDid,
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"issuerDid": "did:cheqd:testnet:11ceabbd-1fdc-46c0-a15d-534c07926d2b",
"subjectDid": "did:key:z6MkqJNR1DHxX2qxqDYx9tNDsXoNRVpaVvJkLPeCYqaARz1n",
"attributes": {
"name": "Bob"
},
"@context": ["https://www.w3.org/2018/credentials/v1"],
"format": "jsonld"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"issuerDid": "did:cheqd:testnet:b86fcd06-9226-49fa-ba58-a0cdebb52e32",
"subjectDid": "did:key:z6MkqJNR1DHxX2qxqDYx9tNDsXoNRVpaVvJkLPeCYqaARz1n",
"attributes": {
"name": "Bob"
},
"@context": ["https://www.w3.org/2018/credentials/v1"],
"format": "jsonld"
}
14 changes: 7 additions & 7 deletions tests/unit/validation/is-jsonld-proof.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { JSONLD_PROOF_TYPES } from '../../../src/types/constants';
const jsonldProofValdiator = new JsonLDProofValidator();

const validProof = {
type: 'Ed25519Signature2018',
created: '2023-12-26T12:44:49Z',
verificationMethod: 'did:cheqd:testnet:4JdgsZ4A8LegKXdsKE3v6X#key-1',
type: 'Ed25519Signature2020',
created: '2024-09-02T11:19:17Z',
verificationMethod: 'did:cheqd:testnet:11ceabbd-1fdc-46c0-a15d-534c07926d2b#key-1',
proofPurpose: 'assertionMethod',
jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..ZUkh4FZ9IcxZK-H6hr0fduq5q4MBYrRfihENJXeJGzqgQkEy16dHwcowbE8NZwNYzmz5MjVJ73q7pkRTg6BvCw',
proofValue: 'z3yauZKryHsVvnW2y8XCB1b993makLQfk1ocKLyhu6w1q2EgeAqAFEhGE1C44XkCoFVPg7r9BK6dTT6P4XCHo6mZp',
};

describe('isJSONLDProofValidator. Positive', () => {
Expand Down Expand Up @@ -46,9 +46,9 @@ describe('isJSONLDProofValidator. Negative', () => {
expect(result.valid).toBe(false);
expect(result.error).toContain('Proof.proofPurpose is required');
});
it('should return false for invalid JSONLD proof. No jws', () => {
const result = jsonldProofValdiator.validate({ ...validProof, jws: undefined } as any);
it('should return false for invalid JSONLD proof. No proofValue', () => {
const result = jsonldProofValdiator.validate({ ...validProof, proofValue: undefined } as any);
expect(result.valid).toBe(false);
expect(result.error).toContain('Proof.jws is required');
expect(result.error).toContain('Proof.proofValue or Proof.jws is required');
});
});

0 comments on commit 60581f6

Please sign in to comment.