Public key cryptography
We use mainly the curve secp256k1 for public-key cryptography, including digital signature and encryption. Here we present a simpler version of our cryptography protocol, the real protocol is quite similar but has some more fields to supports fully private proof verification.
1. Credential format
- A credential issued by an issuer must consist of the below fields:
- Public fields that would be revealed to the public and sent through the unsecured channel
- "issuer": issuer's DID
- "holder": holder's DID
- "type": an array of types of the credentials, e.g. ["RelationshipCredential", "Uni-
versityDegreeCredential"]
- "issuanceDate": the date that issue VC, format by ISO 8601
- "expirationDate" (optional field): the date that VC expired format by ISO 8601
- "encryptedData": encrypted private fields’ data using holder’s public key
- "signatureProof": The signature signed by the issuer, not ZK proof
- "type": "RsaSignature2018"/"EcdsaSecp256k1Signature2019"/...,
- "created": ISO 8601 date format
- "verificationMethod": verification method, corresponding to the signature sheme
- "proofPurpose": "assertionMethod"/"authentication"/...,
- "value": "pYw8XNi1..Cky6Ed=", proof = sign(issuer, holder, public fields above)
- "id": SHA256 hash of proof
- Private fields (only issuer and holder knows):
- "credentialSubject": a json containing content of the credential => all these private fields are encrypted under the field encryptedData
- Public fields that would be revealed to the public and sent through the unsecured channel
- Example:
{
"id": "ebfeb1f712ebc6f1c276e12ec21klj23h4o937842iuwlehasdf",
"type": [ "UniversityDegreeCredential" ],
"issuer": "did:solana:17A3AAB832349DA...",
"holder": "did:solana:9DEf34AB8349DA...",
"issuanceDate": "2010-01-01T19:23:24Z",
"expirationDate": "2020-01-01T19:23:24Z",
"credentialSubject": {
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts",
},
"department": "FIT",
"class" : 2024
},
"encryptedData": "asdfnlaksh2l34hqq32987roaidshalskjdaslkdjf239862o34"
"proof": {
"type": "Ed25519Signature2020",
"created": "2022-02-25T14:58:43Z",
"verificationMethod": "Ed25519SignatureVerification2020",
"proofPurpose": "assertionMethod",
"proofValue": "z3zwi434j3HdZv3C3TJxBnwmmiBgrtzRjM2oeDtZyAEkDeNDBeB9Metc
vyB4ZZUvQswKqPdgk5cFSAnyJ3yjvButH"
}
}
2. User flow
2.1 Issuer issues a credential
- Step 1: Issuer fill credential's content
- Step 2: Issuer encrypt private fields with public key of holder to create a new field "encryptedData"
- encryptedData = ECCEncryption(holder's public key) // ECC + Diffie-Hellman method
- Step 3: Generate signature & id
- proof = sign(signature_method, issuer, holder, encryptedData)
- id = sha256(proof.value)
- Step 4: Send the credential (only public fields) to holder -> holder stores credential in local storage
- Step 5: Call the smart contract to create an anchor to the credential
- credentials[id] = {
- issuer: askdfhlaksdf
- holder: lakhewjlkajsdfhasdf
- status: issued/revoked/disputed/...
}
2.2 Verifier creates a schema
Verifier chooses a schema template (e.g. UniversityDegreeCredentialSchema) or creates a new schema and then send to the holder or publish this schema to standardized process for VC verification. The schema should be constructed with the format below:
"verifier": verifier's DID
"checks": an array of check, each check corresponds to one specific credential, which means a schema can be used to asserts multiple credentials. Each check in the array is a JSON object can be presented as below:
<requestedField>: [<operator>, <value>] | <subCheck>
<requestedField>: <value> (*)
+ <operator>: a comparison operator ($GT, $GTE, $LE, $LTE, $EQ, $NE), would scale to arbitrary expression later
+ <value>: the value of check
+ <subCheck>: formatted as same as <check> - which is a recursive check for sub fields of the current value
+ (*): represent equality, (ongoing feature, we aim to build this like in ORM frameworks)"requests": ["index_of_credential_in_checks_field.requestField1", "index_of_credential_in_checks_field.requestField2", ...] => request holder to shows these fields
Example:
{
"verifier": "did:solana:17AB8349DA...",
"checks":[
{
"type": ["$EQ$", "UniversityDegreeCredential"]
"degree" : {
"type": ["$EQ", "BachelorDegree"], // based on the global standard
"name": ["$EQ", "Bachelor of Science and Arts"]
},
"class": ["$GTE", 2023] // only allows guys graduate this year to apply into the job
},
{
"type": ["$EQ", "NationalIdentificationCredential"]
"country": ["$EQ", "US"],
"nationality": ["$EQ", "US"],
"dateOfBirth": ["GTE", "2000-01-01T00:00:00Z"], // only allows guys born in 21'st century
}
],
"requests": ["0.schoolName", "1.name"] // get name & school name
}
2.3 Holder submits credentials to a schema
- Step 1: After getting the schema from the verifier, the holder chooses corresponding credentials that satisfy the checks and requests
- Step 2: Holder generates & encrypt the requested fields (using verifier public key) into encryptedRequestedData (encryptedRequestedData = ECCEncrypt(verifier_pubic_key, requestedData))
- Step 3: Holder generate ZK proof: proof = generateProof(rawCredentials[], public rawSchema, public requestedData)
- This function do the below checks:
- Check if credentials satisfy the schema
- Check if requestedData is the requested fields specified in rawSchema and taken from rawCredentials
- This function do the below checks:
- Step 4: Holder sends ZK proof + encryptedRequestedData to the verifier through public channel or in-secure private channel (like BE)
- schema_submissions[schema_id][proof] = true/false (true means the proof passes the verifier checks)
- Step 5 (Optional) : Holder submit an anchor of the proof to the verifying smart contract.
2.4 Verifier verifies submission
Step 1: Verifier decrypt the requestedData: requestedData = ECCDecrypt(verifier_private_key, encryptedRequestedData)
Step 2: Verifier run the check:
- Verifier verifies ZK proof: verify(proof, rawSchema, requestedData)
- If the check passes, the verifier updates the status of this submission to