Sign User Data and DVR

Prerequisites

Make sure you have key pair consisting of :

  1. publicKeyJWKS

  2. privateKey

Read Generate Key Pair section for detail info.

Overview

We need to sign User Data and Data Verification Request (DVR) before sending a request to the zkPass service. This ensures that the payload is not tampered during transport.

Example Implementation

JWKS

The JSON Web Key Set (JWKS) is a set of keys containing the public keys used to verify any JSON Web Token (JWT) issued by the Authorization Server and signed, You can find a complete definition of JWKS here.

Upload your publicKeyJWKS so that it's accessible from the internet. This will be used by zkPass service to verify the validity of the user data.

Example of the uploaded publicKeyJWKS

{
  "keys": [
    {
      "x": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELOmrNI4A9ML4iGJXpYlaZiYGVCxB",
      "y": "k+evjhOZEbCLj17o/ZdfEv7dUZIRKRoZ1bud5Gq8OCItDlXkTyMrtWrhdA==",
      "kid": "q6ZFSOJcTiZWJWkvUshpFw5v20xstZN/T4lt4zpKsUg="
    }
  ]
}

JWS

A JSON Web Signature (abbreviated JWS) is an IETF-proposed standard (RFC 7515) for signing arbitrary data. This is used as the basis for a variety of web-based technologies including JSON Web Token. You can find a complete definition of JWS here.

This is an example code of how you can sign a JSON object as JWS format in Typescript. Let's say you uploaded your publicKeyJWKS to https://mywebsite/my-keys.json

import { SignJWT, importPKCS8 } from "jose";

async signDataToJwsToken(
    privateKey: string,
    userDataOrDVR: any
  ): Promise<string> {
    const verifyingKeyJwks = {
      jku: "https://mywebsite/my-keys.json",
      kid: "q6ZFSOJcTiZWJWkvUshpFw5v20xstZN/T4lt4zpKsUg="
    }
    
    const alg = "ES256";
    const importedPrivateKey = await importPKCS8(privateKey, alg);
    
    return await new SignJWT({ data: userDataOrDVR })
      .setProtectedHeader({
        alg: alg,
        jku: verifyingKeyJwks.jku,
        kid: verifyingKeyJwks.kid,
      })
      .sign(importedPrivateKey);
  }

Example for User Data

{
  "testID": "SCREEN-7083-12345",
  "testName": "QualityHealth Comprehensive Screen",
  "testDate": "2023-08-27T14:00:00Z",
  "lab": {
    "name": "QualityHealth Labs",
    "ID": "QH801874",
    "address": "1234 Elm St, Oakland, USA"
  },
  "subject": {
    "firstName": "Jane",
    "lastName": "Doe",
    "dateOfBirth": "1985-12-12",
    "bloodType": "A+",
    "DNAInfo": {
      "markers": {
        "APOE": ["E3", "E3"],
        "BRCA1": "Normal",
        "MTHFR": ["C677T", "A1298C"]
      },
      "haplogroups": {
        "paternal": "R1b1",
        "maternal": "H1a1"
      }
    },
    "contact": {
      "email": "jane.doe@gmail.com",
      "phone": "650-555-1234"
    },
    "address": {
      "street": "789 Oak Street",
      "city": "San Jose",
      "state": "CA",
      "zip": "95134"
    }
  },
  "measuredPanelsNgML": {
    "amphetamines": 0,
    "cocaine": 8,
    "opiates": 102,
    "benzodiazepines": 0
  }
}

Below is the example of Query in DVR, this query will be included in full DVR

[
  {
    "assign": {
      "blood_test_status": {
        "and": [
          {
            "==": [{ "dvar": "lab.ID" }, "QH801874"]
          },
          {
            "==": [{ "dvar": "testID" }, "SCREEN-7083-12345"]
          },
          {
            "~==": [{ "dvar": "subject.firstName" }, "jane"]
          },
          {
            "~==": [{ "dvar": "subject.lastName" }, "doe"]
          },
          {
            "==": [{ "dvar": "subject.dateOfBirth" }, "1985-12-12"]
          },
          {
            "==": [{ "dvar": "measuredPanelsNgML.amphetamines" }, 0]
          },
          {
            "<=": [{ "dvar": "measuredPanelsNgML.cocaine" }, 10]
          }
        ]
      }
    }
  },
  { "output": { "result": { "lvar": "blood_test_status" } } }
]

Below is the example of full DVR that will be generated into DVR Token

{
  "zkvm": "r0",
  "dvr_title": "My DVR",
  "dvr_id": "47343cd7-a2be-404b-811a-ab1c6307c9f9",
  "query_engine_ver": "1.0.0",
  "query_method_ver": "9b7ae23b4cdbc3272a83a8b0928f1f9d778659753cb530583e55949cdec0c7cf",
  "query": "[{\"assign\":{\"blood_test_status\":{\"and\":[{\"==\":[{\"dvar\":\"lab.ID\"},\"QH801874\"]},{\"==\":[{\"dvar\":\"testID\"},\"SCREEN-7083-12345\"]},{\"~==\":[{\"dvar\":\"subject.firstName\"},\"jane\"]},{\"~==\":[{\"dvar\":\"subject.lastName\"},\"doe\"]},{\"==\":[{\"dvar\":\"subject.dateOfBirth\"},\"1985-12-12\"]},{\"==\":[{\"dvar\":\"subject.contact.email\"},\"jane.doe@gmail.com\"]},{\"==\":[{\"dvar\":\"measuredPanelsNgML.amphetamines\"},0]},{\"<=\":[{\"dvar\":\"measuredPanelsNgML.cocaine\"},10]}]}}},{\"output\":{\"name\":{\"dvar\":\"subject.firstName\"}}},{\"output\":{\"email\":{\"dvar\":\"subject.contact.email\"}}},{\"output\":{\"result\":{\"lvar\":\"blood_test_status\"}}}]",
  "user_data_requests": {
    "": {
      "user_data_url": "https://hostname/api/user_data/",
      "user_data_verifying_key": {
        "KeysetEndpoint": {
          "jku": "https://raw.githubusercontent.com/gl-zkPass/zkpass-sdk/main/docs/zkpass/sample-jwks/issuer-key.json",
          "kid": "k-1",
        }
      }
    }
  },
  "dvr_verifying_key": {
    "KeysetEndpoint": {
      "jku": "https://raw.githubusercontent.com/gl-zkPass/zkpass-sdk/main/docs/zkpass/sample-jwks/verifier-key.json",
      "kid": "k-1"
    }
  }
}

Output Example

After this section you should have :

  1. User Data Token : User Data in JSON Web Signature (JWS) format.

  2. DVR Token : DVR in JSON Web Signature (JWS) format.

Here's the example of User Data Token & DVR Token in JWS format.

{
  "userDataToken": "eyJhbGciOiJFUzI1NiIsImprdSI6Imh0dHBzOi8vZ2RwLWFkbWluLmdpdGh1Yi5pby96a3Bhc3Mtc2RrL3prcGFzcy9zYW1wbGUtandrcy9pc3N1ZXIta2V5Lmpzb24iLCJraWQiOiJrLTEifQ.eyJkYXRhIjp7InRlc3RJRCI6IlNDUkVFTi03MDgzLTEyMzQ1IiwidGVzdE5hbWUiOiJRdWFsaXR5SGVhbHRoIENvbXByZWhlbnNpdmUgU2NyZWVuIiwidGVzdERhdGUiOiIyMDIzLTA4LTI3VDE0OjAwOjAwWiIsImxhYiI6eyJuYW1lIjoiUXVhbGl0eUhlYWx0aCBMYWJzIiwiSUQiOiJRSDgwMTg3NCIsImFkZHJlc3MiOiIxMjM0IEVsbSBTdCwgT2FrbGFuZCwgVVNBIn0sInN1YmplY3QiOnsiZmlyc3ROYW1lIjoiSmFuZSIsImxhc3ROYW1lIjoiRG9lIiwiZGF0ZU9mQmlydGgiOiIxOTg1LTEyLTEyIiwiYmxvb2RUeXBlIjoiQSsiLCJETkFJbmZvIjp7Im1hcmtlcnMiOnsiQVBPRSI6WyJFMyIsIkUzIl0sIkJSQ0ExIjoiTm9ybWFsIiwiTVRIRlIiOlsiQzY3N1QiLCJBMTI5OEMiXX0sImhhcGxvZ3JvdXBzIjp7InBhdGVybmFsIjoiUjFiMSIsIm1hdGVybmFsIjoiSDFhMSJ9fSwiY29udGFjdCI6eyJlbWFpbCI6ImphbmUuZG9lQGdtYWlsLmNvbSIsInBob25lIjoiNjUwLTU1NS0xMjM0In0sImFkZHJlc3MiOnsic3RyZWV0IjoiNzg5IE9hayBTdHJlZXQiLCJjaXR5IjoiU2FuIEpvc2UiLCJzdGF0ZSI6IkNBIiwiemlwIjoiOTUxMzQifX0sIm1lYXN1cmVkUGFuZWxzTmdNTCI6eyJhbXBoZXRhbWluZXMiOjAsImNvY2FpbmUiOjgsIm9waWF0ZXMiOjEwMiwiYmVuem9kaWF6ZXBpbmVzIjowfX19.XPEpOCwUXJKWGP0gUpCc7Efdw1j8FSO9nhGbWO45jMwtOUsG_s4SRdg85TtocGf8R5wMXrFDwlppqRkOjeCjpg",
  "dvrToken": "eyJhbGciOiJFUzI1NiIsImprdSI6Imh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9nbC16a1Bhc3MvemtwYXNzLXNkay9tYWluL2RvY3MvemtwYXNzL3NhbXBsZS1qd2tzL3ZlcmlmaWVyLWtleS5qc29uIiwia2lkIjoiay0xIn0.eyJkYXRhIjp7Inprdm0iOiJyMCIsImR2cl90aXRsZSI6Ik15IERWUiIsImR2cl9pZCI6IjEwNjdjNjA2LTkwYWYtNDI2OS05MjE2LWZkMTFmNzBlOTgzZiIsInF1ZXJ5X2VuZ2luZV92ZXIiOiIwLjMuMC1yYy4xIiwicXVlcnlfbWV0aG9kX3ZlciI6IjFmYjMyZWYzNzljZDc2NmYzYzkzYTk4Y2Y1MjIxMmMzYTA1YmQzYjNhM2Y0MTBmNTg4NzZmNmYyMjhmNGU5MyIsInF1ZXJ5IjoiW3tcImFzc2lnblwiOntcImJsb29kX3Rlc3Rfc3RhdHVzXCI6e1wiYW5kXCI6W3tcIj09XCI6W3tcImR2YXJcIjpcImxhYi5JRFwifSxcIlFIODAxODc0XCJdfSx7XCI9PVwiOlt7XCJkdmFyXCI6XCJ0ZXN0SURcIn0sXCJTQ1JFRU4tNzA4My0xMjM0NVwiXX0se1wifj09XCI6W3tcImR2YXJcIjpcInN1YmplY3QuZmlyc3ROYW1lXCJ9LFwiamFuZVwiXX0se1wifj09XCI6W3tcImR2YXJcIjpcInN1YmplY3QubGFzdE5hbWVcIn0sXCJkb2VcIl19LHtcIj09XCI6W3tcImR2YXJcIjpcInN1YmplY3QuZGF0ZU9mQmlydGhcIn0sXCIxOTg1LTEyLTEyXCJdfSx7XCI9PVwiOlt7XCJkdmFyXCI6XCJzdWJqZWN0LmNvbnRhY3QuZW1haWxcIn0sXCJqYW5lLmRvZUBnbWFpbC5jb21cIl19LHtcIj09XCI6W3tcImR2YXJcIjpcIm1lYXN1cmVkUGFuZWxzTmdNTC5hbXBoZXRhbWluZXNcIn0sMF19LHtcIjw9XCI6W3tcImR2YXJcIjpcIm1lYXN1cmVkUGFuZWxzTmdNTC5jb2NhaW5lXCJ9LDEwXX1dfX19LHtcIm91dHB1dFwiOntcIm5hbWVcIjp7XCJkdmFyXCI6XCJzdWJqZWN0LmZpcnN0TmFtZVwifX19LHtcIm91dHB1dFwiOntcImVtYWlsXCI6e1wiZHZhclwiOlwic3ViamVjdC5jb250YWN0LmVtYWlsXCJ9fX0se1wib3V0cHV0XCI6e1wicmVzdWx0XCI6e1wibHZhclwiOlwiYmxvb2RfdGVzdF9zdGF0dXNcIn19fV0iLCJ1c2VyX2RhdGFfdXJsIjoiaHR0cHM6Ly9ob3N0bmFtZS9hcGkvdXNlcl9kYXRhLyIsInVzZXJfZGF0YV92ZXJpZnlpbmdfa2V5Ijp7IktleXNldEVuZHBvaW50Ijp7ImprdSI6Imh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9nbC16a1Bhc3MvemtwYXNzLXNkay9tYWluL2RvY3MvemtwYXNzL3NhbXBsZS1qd2tzL2lzc3Vlci1rZXkuanNvbiIsImtpZCI6ImstMSJ9fSwiZHZyX3ZlcmlmeWluZ19rZXkiOnsiS2V5c2V0RW5kcG9pbnQiOnsiamt1IjoiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2dsLXprUGFzcy96a3Bhc3Mtc2RrL21haW4vZG9jcy96a3Bhc3Mvc2FtcGxlLWp3a3MvdmVyaWZpZXIta2V5Lmpzb24iLCJraWQiOiJrLTEifX19fQ.G5moHbBbNbgen5I8-UZDK5cbzQzUkwnp1GTvUxXnPk-q6EpuaJq2Zud_GBesVGjspZTuaZk9J1GJp8ri65M8aw"
}

Last updated