Skip to content

Commit

Permalink
Feat: select disclosure for present (openwallet-foundation#153)
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas <Lukas@hopae.io>
Signed-off-by: Mirko Mollik <mirko.mollik@fit.fraunhofer.de>
Co-authored-by: Mirko Mollik <mirko.mollik@fit.fraunhofer.de>
  • Loading branch information
lukasjhan and cre8 authored Mar 11, 2024
1 parent 9baa93e commit d9eaf44
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 1 deletion.
73 changes: 72 additions & 1 deletion packages/present/src/present.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type PresentationFrame,
SD_SEPARATOR,
} from '@sd-jwt/types';
import type { Disclosure } from '@sd-jwt/utils';
import { Disclosure, SDJWTException } from '@sd-jwt/utils';
import {
createHashMapping,
decodeSdJwt,
Expand All @@ -13,6 +13,7 @@ import {
createHashMappingSync,
decodeSdJwtSync,
unpackSync,
unpackObj,
} from '@sd-jwt/decode';
import type { HasherSync } from '@sd-jwt/types/src/type';

Expand Down Expand Up @@ -138,3 +139,73 @@ export const transformPresentationFrame = <T extends object>(
return acc;
}, []);
};

export type SerializedDisclosure = {
digest: string;
encoded: string;
salt: string;
key: string | undefined;
value: unknown;
};

const createHashMappingForSerializedDisclosure = (
disclosures: SerializedDisclosure[],
) => {
const map: Record<string, Disclosure> = {};
for (let i = 0; i < disclosures.length; i++) {
const disclosure = disclosures[i];
const { digest, encoded, key, salt, value } = disclosure;
// we made Disclosure to fit the interface of unpack
map[digest] = Disclosure.fromArray(
key ? [salt, key, value] : [salt, value],
{ digest, encoded },
);
}
return map;
};

/**
* This function selects the serialized disclosures from the payload
* and array of serialized disclosure based on the presentation frame.
* If you want to know what is serialized disclosures, check type SerializedDisclosure.
* @param payload: Record<string, unknown>
* @param disclosures: SerializedDisclosure[]
* @param presentationFrame: PresentationFrame<T>
*/
export const selectDisclosures = <T extends Record<string, unknown>>(
payload: Record<string, unknown>,
disclosures: SerializedDisclosure[],
presentationFrame: PresentationFrame<T>,
) => {
if (disclosures.length === 0) {
return [];
}

const hashmap = createHashMappingForSerializedDisclosure(disclosures);
const { disclosureKeymap } = unpackObj(payload, hashmap);
const keys = transformPresentationFrame(presentationFrame);

const presentedDisclosures = keys
.map((k) => hashmap[disclosureKeymap[k]])
.filter((d) => d !== undefined);

const selectedDisclosures: SerializedDisclosure[] = presentedDisclosures.map(
(d) => {
const { salt, key, value, _digest } = d;
if (!_digest) {
throw new SDJWTException(
'Implementation error: _digest is not defined',
);
}
return {
digest: _digest,
encoded: d.encode(),
salt,
key,
value,
};
},
);

return selectedDisclosures;
};
153 changes: 153 additions & 0 deletions packages/present/src/test/present.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { describe, expect, test } from 'vitest';
import { digest } from '@sd-jwt/crypto-nodejs';
import {
type SerializedDisclosure,
present,
presentSync,
presentableKeys,
presentableKeysSync,
selectDisclosures,
transformPresentationFrame,
} from '../index';
import { decodeSdJwt, decodeSdJwtSync } from '@sd-jwt/decode';
Expand Down Expand Up @@ -149,4 +151,155 @@ describe('Present tests', () => {
const list = transformPresentationFrame<typeof claims>(obj);
expect(list).toStrictEqual(['address', 'address.city', 'address.street']);
});

test('select disclosures', () => {
const payload = {
lastname: 'Doe',
_sd: [
'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
],
_sd_alg: 'SHA-256',
};

const presentationFrame = {
firstname: true,
//ssn: true,
id: true,
};

const disclosures: SerializedDisclosure[] = [
{
digest: 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
salt: 'b04760b9810823fa',
key: 'firstname',
value: 'John',
},
{
digest: 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
encoded: 'WyJjNTQwZWE4YjJhOTNmZDE1Iiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ',
salt: 'c540ea8b2a93fd15',
key: 'ssn',
value: '123-45-6789',
},
{
digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
salt: '97f055de944af729',
key: 'id',
value: '1234',
},
];

const selected = selectDisclosures(payload, disclosures, presentationFrame);
expect(selected).toStrictEqual([
{
digest: 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
salt: 'b04760b9810823fa',
key: 'firstname',
value: 'John',
},
{
digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
salt: '97f055de944af729',
key: 'id',
value: '1234',
},
]);
});

test('select disclosures no input', () => {
const selected = selectDisclosures({}, [], {});
expect(selected).toStrictEqual([]);
});

test('select disclosures noting return', () => {
const payload = {
lastname: 'Doe',
_sd: [
'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
],
_sd_alg: 'SHA-256',
};

const presentationFrame = {};

const disclosures: SerializedDisclosure[] = [
{
digest: 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
salt: 'b04760b9810823fa',
key: 'firstname',
value: 'John',
},
{
digest: 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
encoded: 'WyJjNTQwZWE4YjJhOTNmZDE1Iiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ',
salt: 'c540ea8b2a93fd15',
key: 'ssn',
value: '123-45-6789',
},
{
digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
salt: '97f055de944af729',
key: 'id',
value: '1234',
},
];
const selected = selectDisclosures(payload, disclosures, presentationFrame);
expect(selected).toStrictEqual([]);
});

test('expect missing digest', () => {
const payload = {
lastname: 'Doe',
_sd: [
'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
],
_sd_alg: 'SHA-256',
};

const presentationFrame = {
firstname: true,
//ssn: true,
id: true,
};

const disclosures: SerializedDisclosure[] = [
//@ts-ignore
{
encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
salt: 'b04760b9810823fa',
key: 'firstname',
value: 'John',
},
{
digest: 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
encoded: 'WyJjNTQwZWE4YjJhOTNmZDE1Iiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ',
salt: 'c540ea8b2a93fd15',
key: 'ssn',
value: '123-45-6789',
},
{
digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
salt: '97f055de944af729',
key: 'id',
value: '1234',
},
];

expect(() =>
selectDisclosures(payload, disclosures, presentationFrame),
).toThrowError('Implementation error: _digest is not defined');
});
});

0 comments on commit d9eaf44

Please sign in to comment.