Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
65d934b
w3c field request (expirationDate proof generation)
volodymyr-basiuk Jul 23, 2025
036d25a
fix query
volodymyr-basiuk Jul 24, 2025
8807e11
update vp for w3c field
volodymyr-basiuk Jul 24, 2025
ca5ad10
sd in test
volodymyr-basiuk Jul 24, 2025
cc0a66f
format
volodymyr-basiuk Jul 24, 2025
c2ccf16
add isW3C option
volodymyr-basiuk Jul 24, 2025
60f5e91
undo
volodymyr-basiuk Jul 24, 2025
1641e6c
buildFieldPath fix
volodymyr-basiuk Jul 24, 2025
19ae3a1
format
volodymyr-basiuk Jul 24, 2025
9407eba
Merge branch 'main' into feat/w3c-field-request
volodymyr-basiuk Jul 24, 2025
418d08f
fix verification
volodymyr-basiuk Jul 24, 2025
e35090d
fix build
volodymyr-basiuk Jul 24, 2025
cdc692e
fix hardcoded
volodymyr-basiuk Jul 24, 2025
0801965
remove redundant checks
volodymyr-basiuk Jul 24, 2025
c31dd25
move parsing to one function
volodymyr-basiuk Jul 25, 2025
d4b04e7
prettier
volodymyr-basiuk Jul 25, 2025
bd62d94
fix StateProof import in common.ts
volodymyr-basiuk Jul 25, 2025
dfdb64d
use type instead of enum
volodymyr-basiuk Jul 25, 2025
742e95d
add issuanceDate
volodymyr-basiuk Jul 25, 2025
0bcdf81
flattenNestedObject
volodymyr-basiuk Jul 25, 2025
58ec2a4
fix query
volodymyr-basiuk Jul 25, 2025
650c8f1
refactor
volodymyr-basiuk Jul 25, 2025
5e60a04
parseJsonDocumentObject reuse
volodymyr-basiuk Jul 25, 2025
1f0fcbc
add proposal for revocationNonce query request
volodymyr-basiuk Jul 28, 2025
590be12
fix type in vp
volodymyr-basiuk Jul 28, 2025
549e3fc
add test schema
volodymyr-basiuk Jul 28, 2025
000bc7e
fix merklization
volodymyr-basiuk Jul 29, 2025
cd2fa80
'credentialStatus.revocationNonce'
volodymyr-basiuk Jul 29, 2025
9959cb7
rm hardcoded
volodymyr-basiuk Jul 29, 2025
c3effe3
credentialStatus: {} SD for whole object
volodymyr-basiuk Jul 29, 2025
1a4760a
support custom fields
volodymyr-basiuk Jul 29, 2025
ed9b1c9
fix query
volodymyr-basiuk Jul 30, 2025
3113620
preprocess
volodymyr-basiuk Jul 30, 2025
9ace6eb
SD of credentialSubject
volodymyr-basiuk Jul 30, 2025
1eee921
fix
volodymyr-basiuk Jul 30, 2025
129b09b
always createVerifiablePresentation
volodymyr-basiuk Jul 30, 2025
5274089
flattened*
volodymyr-basiuk Jul 30, 2025
8be60e4
vp always exists
volodymyr-basiuk Jul 30, 2025
1861e31
add context to vp in unit tests
volodymyr-basiuk Jul 30, 2025
5a628ab
update IDEN3_PROOFS_DEFINITION_DOCUMENT
volodymyr-basiuk Jul 31, 2025
92ae68d
check $eq in revNonce
volodymyr-basiuk Jul 31, 2025
9282aa1
fix VP and add more tests
volodymyr-basiuk Aug 7, 2025
c6800ce
VP credentialStatus fix unit test
volodymyr-basiuk Aug 7, 2025
b57e4db
Merge branch 'main' into feat/w3c-field-request
volodymyr-basiuk Aug 7, 2025
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
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion src/iden3comm/types/protocol/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,25 @@ export type ZeroKnowledgeProofRequest = {
};

/** ZeroKnowledgeProofQuery represents structure of zkp request query object */
export type ZeroKnowledgeProofQuery = {
export type ZeroKnowledgeProofQuery = W3CV1ProofQueryFields & {
allowedIssuers: string[];
context: string;
credentialSubject?: JsonDocumentObject;
credentialSubjectFullDisclosure?: boolean;
proofType?: ProofType;
skipClaimRevocationCheck?: boolean;
groupId?: number;
type: string;
};

/** W3CV1ProofQueryFields represents fields for W3C v1 ZKP proof query */
export type W3CV1ProofQueryFields = {
expirationDate?: JsonDocumentObject;
issuanceDate?: JsonDocumentObject;
credentialStatus?: JsonDocumentObject;
credentialStatusFullDisclosure?: boolean;
};

export type ZeroKnowledgeInvokeResponse = {
responses: ZeroKnowledgeProofResponse[];
crossChainProof?: CrossChainProof;
Expand All @@ -88,6 +97,11 @@ export type VerifiablePresentation = {
'@context': string | string[];
type: string | string[];
credentialSubject: JsonDocumentObject;
credentialStatus?: {
id?: string;
type?: string;
revocationNonce?: number;
};
};
};

Expand Down
208 changes: 198 additions & 10 deletions src/proof/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
Operators,
QueryOperators
} from '../circuits';
import { StateProof } from '../storage/entities/state';
import {
MerkleTreeProofWithTreeState,
RevocationStatus,
W3CCredential,
buildFieldPath,
getSerializationAttrFromContext,
getFieldSlotIndex
getFieldSlotIndex,
VerifiableConstants,
ProofQuery,
CredentialStatusType
} from '../verifiable';
import { Merklizer, Options, Path } from '@iden3/js-jsonld-merklization';
import { byteEncoder } from '../utils';
import { JsonDocumentObject } from '../iden3comm';
import { JsonDocumentObject, VerifiablePresentation, ZeroKnowledgeProofQuery } from '../iden3comm';
import { Claim } from '@iden3/js-iden3-core';
import { poseidon } from '@iden3/js-crypto';
import { StateProof } from '../storage';

export type PreparedCredential = {
credential: W3CCredential;
Expand Down Expand Up @@ -101,8 +104,11 @@
fieldName: string;
operator: Operators;
operatorValue?: unknown;
kind?: PropertyQueryKind;
};

export type PropertyQueryKind = 'credentialSubject' | 'w3cV1';

export type QueryMetadata = PropertyQuery & {
slotIndex: number;
values: bigint[];
Expand All @@ -112,14 +118,144 @@
merklizedSchema: boolean;
};

export const parseCredentialSubject = (credentialSubject?: JsonDocumentObject): PropertyQuery[] => {
// credentialSubject is empty
if (!credentialSubject) {
return [{ operator: QueryOperators.$noop, fieldName: '' }];
export const parseZKPQuery = (query: ZeroKnowledgeProofQuery): PropertyQuery[] => {
const propertiesMetadata = parseCredentialSubject(query.credentialSubject as JsonDocumentObject);
if (query.credentialSubjectFullDisclosure) {
propertiesMetadata.push({
operator: QueryOperators.$sd,
fieldName: 'credentialSubject',
kind: 'w3cV1'
});
}
if (query.expirationDate) {
const expirationDate = parseJsonDocumentObject(
{ expirationDate: query.expirationDate },
'w3cV1'
);
propertiesMetadata.push(...expirationDate);
}
if (query.issuanceDate) {
const issuanceDate = parseJsonDocumentObject({ issuanceDate: query.issuanceDate }, 'w3cV1');
propertiesMetadata.push(...issuanceDate);
}
if (query.credentialStatus) {
const flattenedObject = flattenNestedObject(
query.credentialStatus as Record<string, JsonDocumentObject | undefined>,
'credentialStatus'
);
propertiesMetadata.push(...parseJsonDocumentObject(flattenedObject, 'w3cV1'));
if (query.credentialStatusFullDisclosure) {
propertiesMetadata.push({
operator: QueryOperators.$sd,
fieldName: 'credentialStatus',
kind: 'w3cV1'
});
}
}
return propertiesMetadata;
};

const flattenNestedObject = (
input: Record<string, JsonDocumentObject | undefined>,
parentKey: string
): Record<string, JsonDocumentObject> => {
const result: Record<string, JsonDocumentObject> = {};

for (const [key, value] of Object.entries(input)) {
if (value !== undefined) {
result[`${parentKey}.${key}`] = value;
}
}
return result;
};

const parseCredentialStatus = (
document?: JsonDocumentObject,
vp?: VerifiablePresentation
): PropertyQuery[] => {
const fieldName = 'credentialStatus';
const kind = 'w3cV1';
if (!document) {
return [{ operator: QueryOperators.$noop, fieldName: '', kind }];
}
// if document is empty, full disclosure is needed
if (Object.entries(document).length === 0) {
if (!vp) {
throw new Error(`VerifiablePresentation is required for full disclosure of credentialStatus`);
}
const queries: PropertyQuery[] = [];
queries.push({ operator: QueryOperators.$sd, fieldName, kind });
const flattened = flattenToQueryShape(
(vp.verifiableCredential as Record<string, any>)[fieldName],

Check warning on line 189 in src/proof/common.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
fieldName
);
queries.push(...parseJsonDocumentObject(flattened, kind));
return queries;
}
const flattenedObject = flattenNestedObject(
document as Record<string, JsonDocumentObject | undefined>,
fieldName
);
return parseJsonDocumentObject(flattenedObject, kind);
};

const parseCredentialSubjectWithVP = (
document?: JsonDocumentObject,
vp?: VerifiablePresentation
): PropertyQuery[] => {
const fieldName = 'credentialSubject';
const kind = 'credentialSubject';
if (!document) {
return [{ operator: QueryOperators.$noop, fieldName: '', kind }];
}
// if document is empty, full disclosure is needed
if (Object.entries(document).length === 0) {
if (!vp) {
throw new Error(
`VerifiablePresentation is required for full disclosure of credentialSubject`
);
}
const queries: PropertyQuery[] = [];
queries.push({ operator: QueryOperators.$sd, fieldName, kind: 'w3cV1' });
const flattened = flattenToQueryShape(
(vp.verifiableCredential as Record<string, any>)[fieldName]

Check warning on line 221 in src/proof/common.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
);
queries.push(...parseJsonDocumentObject(flattened, kind));
return queries;
}
return parseJsonDocumentObject(document, kind);
};

export const flattenToQueryShape = (
obj: Record<string, any>,

Check warning on line 230 in src/proof/common.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
parentKey = ''
): JsonDocumentObject => {
const result: JsonDocumentObject = {};
for (const [key, value] of Object.entries(obj)) {
if (key === 'id' || key === 'type') {
continue;
}
const fullKey = parentKey ? `${parentKey}.${key}` : key;
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
Object.assign(result, flattenToQueryShape(value, fullKey));
} else {
result[fullKey] = {};
}
}
return result;
};

export const parseJsonDocumentObject = (
document?: JsonDocumentObject,
kind?: PropertyQueryKind
): PropertyQuery[] => {
// document is empty
if (!document) {
return [{ operator: QueryOperators.$noop, fieldName: '', kind }];
}

const queries: PropertyQuery[] = [];
const entries = Object.entries(credentialSubject);
const entries = Object.entries(document);
if (!entries.length) {
throw new Error(`query must have at least 1 predicate`);
}
Expand All @@ -130,7 +266,7 @@
const isSelectiveDisclosure = fieldReqEntries.length === 0;

if (isSelectiveDisclosure) {
queries.push({ operator: QueryOperators.$sd, fieldName: fieldName });
queries.push({ operator: QueryOperators.$sd, fieldName, kind });
continue;
}

Expand All @@ -139,18 +275,32 @@
throw new Error(`operator is not supported by lib`);
}
const operator = QueryOperators[operatorName as keyof typeof QueryOperators];
queries.push({ operator, fieldName, operatorValue });
queries.push({ operator, fieldName, operatorValue, kind });
}
}
return queries;
};

export const parseCredentialSubject = (credentialSubject?: JsonDocumentObject): PropertyQuery[] => {
return parseJsonDocumentObject(credentialSubject, 'credentialSubject');
};

export const parseQueryMetadata = async (
propertyQuery: PropertyQuery,
ldContextJSON: string,
credentialType: string,
options: Options
): Promise<QueryMetadata> => {
const replacedFieldName = propertyQuery.fieldName;
if (propertyQuery?.kind === 'w3cV1') {
if (Object.values(CredentialStatusType).includes(credentialType as CredentialStatusType)) {
propertyQuery.fieldName = propertyQuery.fieldName.replace('credentialStatus.', '');
ldContextJSON = VerifiableConstants.JSONLD_SCHEMA.IDEN3_PROOFS_DEFINITION_DOCUMENT;
} else {
ldContextJSON = VerifiableConstants.JSONLD_SCHEMA.W3C_VC_DOCUMENT_2018;
credentialType = VerifiableConstants.CREDENTIAL_TYPE.W3C_VERIFIABLE_CREDENTIAL;
}
}
const query: QueryMetadata = {
...propertyQuery,
slotIndex: 0,
Expand Down Expand Up @@ -197,6 +347,7 @@
ldContextJSON,
credentialType,
propertyQuery.fieldName,
propertyQuery.kind,
options
);
query.claimPathKey = await path.mtEntry();
Expand Down Expand Up @@ -234,9 +385,46 @@
}
query.values = values;
}
query.fieldName = replacedFieldName;
return query;
};

export const parseProofQueryMetadata = async (
credentialType: string,
ldContextJSON: string,
query: ProofQuery,
options: Options,
vp?: VerifiablePresentation
): Promise<QueryMetadata[]> => {
const propertyQuery = parseCredentialSubjectWithVP(query.credentialSubject, vp);
if (query.expirationDate) {
propertyQuery.push(
...parseJsonDocumentObject({ expirationDate: query.expirationDate }, 'w3cV1')
);
}
if (query.issuanceDate) {
propertyQuery.push(...parseJsonDocumentObject({ issuanceDate: query.issuanceDate }, 'w3cV1'));
}

if (query.credentialStatus) {
const credSubject = parseCredentialStatus(query.credentialStatus, vp);
propertyQuery.push(...credSubject);
}

return Promise.all(
propertyQuery.map((p) => {
let credType = credentialType;
if (p?.kind === 'w3cV1' && p.fieldName.startsWith('credentialStatus.')) {
if (!vp?.verifiableCredential?.credentialStatus?.type) {
throw new Error('credentialStatus.type is required for w3cV1 queries');
}
credType = vp?.verifiableCredential?.credentialStatus?.type;
}
return parseQueryMetadata(p, ldContextJSON, credType, options);
})
);
};

export const parseQueriesMetadata = async (
credentialType: string,
ldContextJSON: string,
Expand Down
Loading
Loading