UNPKG

@0xpolygonid/js-sdk

Version:
274 lines (245 loc) 7.52 kB
import { NodeAux, Hash, Proof, ZERO_HASH } from '@iden3/js-merkletree'; import { buildTreeState, ClaimNonRevStatus, GISTProof, isValidOperation, Operators, QueryOperators } from '../circuits'; import { StateProof } from '../storage/entities/state'; import { MerkleTreeProofWithTreeState, RevocationStatus, W3CCredential, buildFieldPath, getSerializationAttrFromContext, getFieldSlotIndex } from '../verifiable'; import { Merklizer, Options, Path } from '@iden3/js-jsonld-merklization'; import { byteEncoder } from '../utils'; import { JsonDocumentObject } from '../iden3comm'; import { Claim } from '@iden3/js-iden3-core'; import { poseidon } from '@iden3/js-crypto'; export type PreparedCredential = { credential: W3CCredential; credentialCoreClaim: Claim; revStatus?: RevocationStatus; }; export type PreparedAuthBJJCredential = { credential: W3CCredential; incProof: MerkleTreeProofWithTreeState; nonRevProof: MerkleTreeProofWithTreeState; coreClaim: Claim; }; /** * converts verifiable RevocationStatus model to circuits structure * * @param {RevocationStatus} - credential.status of the verifiable credential * @returns {ClaimNonRevStatus} */ export const toClaimNonRevStatus = (s?: RevocationStatus): ClaimNonRevStatus => { if (!s) { const hash = poseidon.hash(new Array(3).fill(0n)); return { proof: new Proof(), treeState: { state: Hash.fromBigInt(hash), claimsRoot: ZERO_HASH, revocationRoot: ZERO_HASH, rootOfRoots: ZERO_HASH } }; } return { proof: s.mtp, treeState: buildTreeState( s.issuer.state, s.issuer.claimsTreeRoot, s.issuer.revocationTreeRoot, s.issuer.rootOfRoots ) }; }; /** * converts state info from smart contract to gist proof * * @param {StateProof} smtProof - state proof from smart contract * @returns {GISTProof} */ export const toGISTProof = (smtProof: StateProof): GISTProof => { let existence = false; let nodeAux: NodeAux | undefined; if (smtProof.existence) { existence = true; } else { if (smtProof.auxExistence) { nodeAux = { key: Hash.fromBigInt(smtProof.auxIndex), value: Hash.fromBigInt(smtProof.auxValue) }; } } const allSiblings: Hash[] = smtProof.siblings.map((s) => Hash.fromBigInt(s)); const proof = new Proof({ siblings: allSiblings, nodeAux: nodeAux, existence: existence }); const root = Hash.fromBigInt(smtProof.root); return { root, proof }; }; export type PropertyQuery = { fieldName: string; operator: Operators; operatorValue?: unknown; }; export type QueryMetadata = PropertyQuery & { slotIndex: number; values: bigint[]; path: Path; claimPathKey: bigint; datatype: string; merklizedSchema: boolean; }; export const parseCredentialSubject = (credentialSubject?: JsonDocumentObject): PropertyQuery[] => { // credentialSubject is empty if (!credentialSubject) { return [{ operator: QueryOperators.$noop, fieldName: '' }]; } const queries: PropertyQuery[] = []; const entries = Object.entries(credentialSubject); if (!entries.length) { throw new Error(`query must have at least 1 predicate`); } for (const [fieldName, fieldReq] of entries) { const fieldReqEntries = Object.entries(fieldReq as { [key: string]: unknown }); const isSelectiveDisclosure = fieldReqEntries.length === 0; if (isSelectiveDisclosure) { queries.push({ operator: QueryOperators.$sd, fieldName: fieldName }); continue; } for (const [operatorName, operatorValue] of fieldReqEntries) { if (!QueryOperators[operatorName as keyof typeof QueryOperators]) { throw new Error(`operator is not supported by lib`); } const operator = QueryOperators[operatorName as keyof typeof QueryOperators]; queries.push({ operator, fieldName, operatorValue }); } } return queries; }; export const parseQueryMetadata = async ( propertyQuery: PropertyQuery, ldContextJSON: string, credentialType: string, options: Options ): Promise<QueryMetadata> => { const query: QueryMetadata = { ...propertyQuery, slotIndex: 0, merklizedSchema: false, datatype: '', claimPathKey: BigInt(0), values: [], path: new Path() }; if (!propertyQuery.fieldName && propertyQuery.operator !== Operators.NOOP) { throw new Error('query must have a field name if operator is not $noop'); } if (propertyQuery.fieldName) { query.datatype = await Path.newTypeFromContext( ldContextJSON, `${credentialType}.${propertyQuery.fieldName}`, options ); } const serAttr = await getSerializationAttrFromContext( JSON.parse(ldContextJSON), options, credentialType ); if (!serAttr) { query.merklizedSchema = true; } // for merklized credentials slotIndex in query must be equal to zero // and not a position of merklization root. // it has no influence on check in the off-chain circuits, but it aligns with onchain verification standard if (!query.merklizedSchema) { query.slotIndex = await getFieldSlotIndex( propertyQuery.fieldName, credentialType, byteEncoder.encode(ldContextJSON) ); } else { try { const path = await buildFieldPath( ldContextJSON, credentialType, propertyQuery.fieldName, options ); query.claimPathKey = await path.mtEntry(); query.path = path; } catch (e) { throw new Error(`field does not exist in the schema ${(e as Error).message}`); } } if (propertyQuery.operatorValue !== undefined) { if (!isValidOperation(query.datatype, propertyQuery.operator)) { throw new Error( `operator ${propertyQuery.operator} is not supported for datatype ${query.datatype}` ); } if ( (propertyQuery.operator === Operators.NOOP || propertyQuery.operator === Operators.SD) && propertyQuery.operatorValue ) { throw new Error(`operator value should be undefined for ${propertyQuery.operator} operator`); } let values: bigint[]; switch (propertyQuery.operator) { case Operators.NOOP: case Operators.SD: values = []; break; case Operators.EXISTS: values = transformExistsValue(propertyQuery.operatorValue); break; default: values = await transformQueryValueToBigInts(propertyQuery.operatorValue, query.datatype); } query.values = values; } return query; }; export const parseQueriesMetadata = async ( credentialType: string, ldContextJSON: string, credentialSubject: JsonDocumentObject, options: Options ): Promise<QueryMetadata[]> => { const queriesMetadata = parseCredentialSubject(credentialSubject); return Promise.all( queriesMetadata.map((m) => parseQueryMetadata(m, ldContextJSON, credentialType, options)) ); }; export const transformQueryValueToBigInts = async ( value: unknown, ldType: string ): Promise<bigint[]> => { const values: bigint[] = []; if (Array.isArray(value)) { for (let index = 0; index < value.length; index++) { values[index] = await Merklizer.hashValue(ldType, value[index]); } } else { values[0] = await Merklizer.hashValue(ldType, value); } return values; }; const transformExistsValue = (value: unknown): bigint[] => { if (typeof value == 'boolean') { return [BigInt(value)]; } throw new Error('exists operator value must be true or false'); };