Skip to main content

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
  • 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
  • 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