From 21743f7e4c0a1cbb16a11ec28a2afd103b6d6875 Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Mon, 16 Mar 2026 20:47:19 +0530 Subject: [PATCH 1/2] [MOSIP-44586] removed local ISSUE_TEMPLATES Signed-off-by: Chandra Keshav Mishra --- .github/ISSUE_TEMPLATE/bug_report.md | 52 ----------------------- .github/ISSUE_TEMPLATE/feature_request.md | 20 --------- 2 files changed, 72 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 36dfa14eb2..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug, triage -assignees: ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Actual behavior** -A clear and concise description of what factually occurred. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Add the screenshot of the profile page with commit id** - -**Smartphone (please complete the following information):** - -- Wallet Device: [e.g. iPhone6] - - Phone make/model: [e.g. :Vivo Y73] - - OS: [e.g. iOS8.1] - - BLE version : [e.g. 4.2] - -- Verifier Device: [e.g. iPhone6] - - Phone make/model: [e.g. :Vivo Y73] - - OS: [e.g. iOS8.1] - - BLE version : [e.g. 4.2] - -- Inji app version: [e.g 0.3.0] -- Mimoto version: [e.g 1.2.x] -- MOSIP Version: [e.g. 1.2.1] - -**Where does the issue occur: Wallet/Verifier?** - -**Logs of wallet and verifier:** - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 01fb3846df..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement, triage -assignees: - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. From 501c613df01006b73fefb1cfc47665e00baf01f3 Mon Sep 17 00:00:00 2001 From: dhruv1955 Date: Sat, 25 Apr 2026 23:00:46 +0530 Subject: [PATCH 2/2] test: add unit tests for VCProcessor SD-JWT reconstruction Signed-off-by: dhruv1955 --- .talismanrc | 2 + components/VC/common/VCProcessor.test.ts | 254 +++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 components/VC/common/VCProcessor.test.ts diff --git a/.talismanrc b/.talismanrc index 40197197d4..d7fec2aa62 100644 --- a/.talismanrc +++ b/.talismanrc @@ -5,6 +5,8 @@ fileignoreconfig: checksum: e89fb09400c82b0e8bade0c16ad168babd1182685773b982f83bfff250e9fdb4 - filename: components/VC/common/VCProcessor.ts checksum: e4d65665ce4ef34805578961e8dbfd9845ded937bebbe57e6da35098ad31fbd3 + - filename: components/VC/common/VCProcessor.test.ts + checksum: 86473e785955ec292bc7dd714793f9b50c79e437fc98d505feeadca885c3ae6a - filename: components/VC/common/VCUtils.tsx checksum: 7894949f973ea15a532a5fc537e09c5993e5e54b763195302f999afe5fc87d03 - filename: screens/Settings/KeyManagementScreen.tsx diff --git a/components/VC/common/VCProcessor.test.ts b/components/VC/common/VCProcessor.test.ts new file mode 100644 index 0000000000..356458906a --- /dev/null +++ b/components/VC/common/VCProcessor.test.ts @@ -0,0 +1,254 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import base64url from 'base64url'; +import {sha256} from '@noble/hashes/sha2'; +import {VCProcessor, reconstructSdJwtFromCompact} from './VCProcessor'; +import {VCFormat} from '../../../shared/VCFormat'; +import {getVerifiableCredential} from '../../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors'; + +jest.mock('react-native', () => ({ + NativeModules: { + RNPixelpassModule: { + decodeBase64UrlEncodedCBORData: jest.fn(), + }, + }, +})); + +jest.mock( + '../../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors', + () => ({ + getVerifiableCredential: jest.fn(), + }), +); + +const mockedGetVerifiableCredential = getVerifiableCredential as jest.Mock; + +const encodeBase64Url = (value: unknown): string => + Buffer.from(JSON.stringify(value), 'utf-8') + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + +const createDisclosure = (value: unknown[]): string => encodeBase64Url(value); + +const createDigest = (disclosureB64: string): string => + base64url(Buffer.from(sha256(disclosureB64))); + +const createSdJwtCompact = ( + payload: Record, + disclosures: string[], +): string => { + const headerB64 = encodeBase64Url({alg: 'none', typ: 'JWT'}); + const payloadB64 = encodeBase64Url(payload); + const jwt = `${headerB64}.${payloadB64}.signature`; + return [jwt, ...disclosures].join('~'); +}; + +describe('VCProcessor', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('reconstructSdJwtFromCompact', () => { + it('should resolve a simple top-level disclosed claim', () => { + const givenNameDisclosure = createDisclosure([ + 'salt-given', + 'given_name', + 'Alice', + ]); + const givenNameDigest = createDigest(givenNameDisclosure); + + const compact = createSdJwtCompact( + { + _sd: [givenNameDigest], + _sd_alg: 'sha-256', + iss: 'https://issuer.example', + iat: 1710000000, + }, + [givenNameDisclosure], + ); + + const result = reconstructSdJwtFromCompact(compact); + + expect(result.fullResolvedPayload.given_name).toBe('Alice'); + expect(result.disclosedKeys).toContain('given_name'); + expect(result.publicKeys).toEqual(expect.arrayContaining(['iss', 'iat'])); + expect(result.pathToDisclosures.given_name).toEqual([ + givenNameDisclosure, + ]); + expect(result.fullResolvedPayload).not.toHaveProperty('_sd_alg'); + }); + + it('should resolve nested disclosed claims like address.city', () => { + const cityDisclosure = createDisclosure(['salt-city', 'city', 'Paris']); + const cityDigest = createDigest(cityDisclosure); + const addressDisclosure = createDisclosure([ + 'salt-address', + 'address', + { + _sd: [cityDigest], + }, + ]); + const addressDigest = createDigest(addressDisclosure); + + const compact = createSdJwtCompact( + { + _sd: [addressDigest], + _sd_alg: 'sha-256', + }, + [addressDisclosure, cityDisclosure], + ); + + const result = reconstructSdJwtFromCompact(compact); + + expect(result.fullResolvedPayload.address.city).toBe('Paris'); + expect(result.disclosedKeys).toEqual( + expect.arrayContaining(['address', 'address.city']), + ); + expect(result.pathToDisclosures['address.city']).toEqual([ + addressDisclosure, + cityDisclosure, + ]); + }); + + it('should resolve array disclosures like nationalities', () => { + const nationalityItemDisclosure = createDisclosure(['salt-item', 'IN']); + const nationalityItemDigest = createDigest(nationalityItemDisclosure); + const nationalitiesDisclosure = createDisclosure([ + 'salt-nationalities', + 'nationalities', + [ + { + '...': nationalityItemDigest, + }, + ], + ]); + const nationalitiesDigest = createDigest(nationalitiesDisclosure); + + const compact = createSdJwtCompact( + { + _sd: [nationalitiesDigest], + _sd_alg: 'sha-256', + }, + [nationalitiesDisclosure, nationalityItemDisclosure], + ); + + const result = reconstructSdJwtFromCompact(compact); + + expect(result.fullResolvedPayload.nationalities).toEqual(['IN']); + expect(result.disclosedKeys).toEqual( + expect.arrayContaining(['nationalities', 'nationalities[0]']), + ); + expect(result.pathToDisclosures['nationalities[0]']).toEqual([ + nationalitiesDisclosure, + nationalityItemDisclosure, + ]); + }); + + it('should skip undisclosed claims without crashing', () => { + const compact = createSdJwtCompact( + { + _sd: ['digest-without-disclosure'], + _sd_alg: 'sha-256', + iss: 'https://issuer.example', + }, + [], + ); + + const result = reconstructSdJwtFromCompact(compact); + + expect(result.fullResolvedPayload).not.toHaveProperty( + 'digest-without-disclosure', + ); + expect(result.disclosedKeys).toEqual([]); + expect(result.fullResolvedPayload.iss).toBe('https://issuer.example'); + }); + + it('should throw for malformed compact SD-JWT input', () => { + expect(() => reconstructSdJwtFromCompact('malformed-input')).toThrow(); + }); + }); + + describe('processForRendering', () => { + it('should process vc+sd-jwt and populate fullResolvedPayload', async () => { + const givenNameDisclosure = createDisclosure([ + 'salt-given', + 'given_name', + 'Alice', + ]); + const givenNameDigest = createDigest(givenNameDisclosure); + const compact = createSdJwtCompact( + { + _sd: [givenNameDigest], + _sd_alg: 'sha-256', + iss: 'https://issuer.example', + }, + [givenNameDisclosure], + ); + + const result = await VCProcessor.processForRendering( + {credential: compact} as any, + VCFormat.vc_sd_jwt, + ); + + expect(result.fullResolvedPayload.given_name).toBe('Alice'); + expect(result.disclosedKeys).toContain('given_name'); + expect(result.publicKeys).toContain('iss'); + expect(result.pathToDisclosures.given_name).toEqual([ + givenNameDisclosure, + ]); + }); + + it('should process dc+sd-jwt the same way as vc+sd-jwt', async () => { + const familyNameDisclosure = createDisclosure([ + 'salt-family', + 'family_name', + 'Doe', + ]); + const familyNameDigest = createDigest(familyNameDisclosure); + const compact = createSdJwtCompact( + { + _sd: [familyNameDigest], + _sd_alg: 'sha-256', + sub: 'did:example:abc', + }, + [familyNameDisclosure], + ); + + const result = await VCProcessor.processForRendering( + {credential: compact} as any, + VCFormat.dc_sd_jwt, + ); + + expect(result.fullResolvedPayload.family_name).toBe('Doe'); + expect(result.disclosedKeys).toContain('family_name'); + expect(result.publicKeys).toContain('sub'); + expect(result.pathToDisclosures.family_name).toEqual([ + familyNameDisclosure, + ]); + }); + + it('should not use SD-JWT path for ldp_vc format', async () => { + const expectedCredential = { + credentialSubject: { + id: '123', + }, + }; + mockedGetVerifiableCredential.mockReturnValue(expectedCredential); + + const vcData = { + credential: 'plain-vc-data', + } as any; + + const result = await VCProcessor.processForRendering( + vcData, + VCFormat.ldp_vc, + ); + + expect(mockedGetVerifiableCredential).toHaveBeenCalledTimes(1); + expect(mockedGetVerifiableCredential).toHaveBeenCalledWith(vcData); + expect(result).toEqual(expectedCredential); + expect(result).not.toHaveProperty('fullResolvedPayload'); + }); + }); +});