Skip to main content

ZK Core Protocol

Problem

The challenge here is, we want to enable holder to submit VCs that satisfy checks and share the requested fields based on what are specified in the verification schema. The point is if we don't share the private fields, how can we prove that the fields satisfy checks?

Solution

We did some thorough experiments and came up with a simple idea: Each credentials and verification schemas consist of single fields. We need to prove multiple comparisons of the form (credential_field_value, operator, schema_single_field_check_value). When generating a ZK proof, holder can supply these raw data into ZK engine and it will execute the comparison to check if the supplied credentials satisfy the schema.

To ensure that holder supplies correct values (not omit or forge the schema checks), each VC and each schema has their own published field index list. The idea is: we give each field in a JSON object a distinct index, and then we put all these indexes into a Spare Merkle Tree to store the unique proof of inclusion of all the fields. Each VC and verification schema has their own field index SMT. The holder must supply the SMT roots into the ZK engine as public inputs. The verifier then just supply exact same roots of VCs and schema to verify the proof. This helps ensure the fields provided by holders are not faked ones and they satisfy all the single field checks in the schema. The issuers publish the SMT for the VCs they issue and the verifiers publish the SMT for the schemas they create.

export enum ProofPurpose {
ASSERTION = 'ASSERTION',
AUTHENTICATION = 'AUTHENTICATION',
}
export interface DataSignature<ECCCurvePoint> {
type: string;
created: string;
proofPurpose: ProofPurpose;
value: string;
verificationMethod: string;
}
export interface FieldIndexInterface {
fieldName: string;
fieldIndex: number;
}
export interface CredentialInterface {
id: string;
types: string[];
issuer: string;
issuerPublicKey: string;
holderPublicKey: string;
holder: string;
issuanceDate: string;
expirationDate?: string;
fieldIndexes: Array<FieldIndexInterface>;
fieldMerkleRoot: string;
credentialSubject?: any;
encryptedData: string;
signatureProof: DataSignature<P>;
}
export interface SchemaInterface{
id: string;
name: string;
verifier: string;
credentialChecks: Array<SchemaCredentialCheck<P>>;
requestedFields: string[];
signatureProof: DataSignature<P>;
issuanceDate: string;
verifierPublicKey: string;
}

In this manner, the verifier does not need to pass the raw values of credentials, SMT roots are enough, so the holder can hide the raw data of the credentials.

In out previous experiments, we put multiple credentials and schemas into a single proof, but in this project, we build a new method to generate and verify proofs more efficiently: which is to build proof of single field checks separately, so we could generate proofs of single field validations concurrently, which boosts the speed of proof generation and verification.

Here is the example public parameters of our circuits, there is no raw data of credentials needed, only the SMT roots is shared to the verifier:

component main {public [credentialRoot, schemaCheckRoot, credentialFieldIndex, schemaCheckFieldIndex, schemaCheckFieldOperation ]} = VCFieldVerifier(4, 6);

Some benchmarks

Currently, in the deployed version, we only support limited number of fields:

  • Each credential should not have more than 32 fields to be safe
  • Each schema can have unlimited credential check but each credential check should have no more than 32 field constraints to be safe
  • The number of ZK constraints generated (for a single field validation) is around 16k which is small enough. Proof generation time is about 2 - 5 seconds and verification time is 1s. This is quite a good number that we have achieved.
  • Our app allows holders to generate proof of credentials right on the mobile app (React Native). Actually the circom library does not work well on React Native since the ffjs library relies heavily on Wasm and it does not work on RN. We decided to use Webview to gen proof instead, this is good enough in terms of performance and UX.

Some problems and future goals

  • Experiment with some more lightweight primitives other than SMT to increase the number of fields supported and to boost the proof generation time down to less than 2s. The final goal is instant proof generation, maybe ZK is not the final solution to achieve that.
  • Explore some delegated proof generation/verification approach.
  • Building "Sign in with DID/VC" feature to replace traditional Oauth methods which lack of privacy and data-ownership for users.
  • Since our DID and VC solution rely on blockchain and the data is spread across multiple platforms, we are aiming to build a general solution to help users to use DID and VC on every platforms, even on the internet without spending much time to learn about each platform. This for sure needs a lot of research and time to construct that form of general solution but we believe it is still possible using proofs of VCs/DIDs or proofs of data ownership.