Public API

This API is work in progress and subject to change

Server

All the API requests must be sent to the base URL {{zkportal_backend}}.

We also have a test backend for you to use at base URL: {{zkportal_backend_test}}.

Please reach out to us first for an API key!

The test backend has 2 accounts added for all app integrations: 0x9db3fb0d6fa378ab7a51fef1dbf2aacd78108129 and 0xaa8f86e0e1cbeddeb30bf0ecfb1a795e03c21690.

The first one will have valid proofs of country and age. These valid proofs are taken directly from zk-verifier examples.

The second one will have invalid proofs, meaning that proof validation will fail. In this case, the proofs are exactly the same as for the first account but the public inputs for the proofs were changed to fail validation.

In the example, the country proof public input is a country blacklist USIRRU + user hash in the format described in this section. For verification to fail, the country blacklist part was changed to NLIRRU and encoded the same way. Since the private input to the proof was NL, the verification will fail.

In the example, the minimum age proof public input is a Unix timestamp for minimum required date of birth 1669637350 + user hash in the format described in this section. Since the example used 1669637349 as user's date of birth, in order for verification to fail, the user age timestamp must be bigger that the minimum age timestamp. For verification to fail, the minimum age timestamp part was changed to 1669637348, which is 1 second less that the user age. That means that the "user" is 1 second younger (1669637349 > 1669637348) than required, thus failing the proof verification.

Authentication

All endpoints require an API key passed in the Authorization header.

Authorization: Bearer yourapikey

At the moment, we do not support API key generation. Please contact info@zkportal.io to get an API key.

An API key is linked to a certain Application. A user needs to link their Ethereum account to that application in zkPortal app before their data becomes visible for requests from that application.

Errors

All endpoints follow the same format for errors

{
  "code": 12345,
  "description": "Error description"
}

See Error Codes for error descriptions.

Common errors

All erroneous HTTP responses will come with a JSON body with error description and the code.

400 Bad Request

Possible reasons:

  • Missing a required query parameter
  • Invalid query parameter

403 Forbidden

Possible reasons:

  • API key is missing in the request
  • API key is not found in the database
  • API key is invalid
  • (For the future) API key has expired

404 Not Found

Possible reasons:

  • Not using the right endpoint
  • User doesn't exist

412 Precondition Failed

Possible reasons:

  • The backend is not running in TEE
  • The backend doesn't have access to one of the required services

429 Too Many Requests

The currently used limit is 100 RPS per IP.

500 Internal Server Error

Something went wrong on our side, please contact us with the details

API

This API produces application/json responses.

The API provides proofs of country and age if the user has submitted them.

Proof of country - a ZK proof that user's country is not in the ban list (specific to apps). A proof signature is created over country blacklist and hash of random number + user's country.

Proof of age - a ZK proof that the user is at least 18 years old. A proof signature is created over minimum age timestamp and hash of random number + user's date of birth timestamp (with hours, minutes, and seconds set to 0).

See Schema section for definitions of objects used in responses, e.g. GeneralProofObject.

About proving/verifying keys

We have generated static proving and verifying keys, a pair for each type of proof. zkPortal mobile app fetches them and uses to generate user's ZK proofs.

For convenience, we provide an API endpoint, which returns proving and verifying keys for all types of proofs we use. See the section below.

Get proving and verifying keys

The following endpoints serve static files:

  • {{zkportal_backend}}/static/age_proofs/proving_key
  • {{zkportal_backend}}/static/age_proofs/verifying_key
  • {{zkportal_backend}}/static/country_proofs/proving_key
  • {{zkportal_backend}}/static/country_proofs/verifying_key

API key: not required

Content type: application/octet-stream

Compression: not supported

Supported headers:

  • Range - supported unit is bytes

Health

Request GET /health

API key: not required

Response:

{
  "apiVersion": 1,
  "isTee": true|false, // whether the backend is running in an enclave (SGX)
  "teeData": {
    "debug": true|false, // whether the enclave is running in debug mode
    "productId": 1, // uint16, unique identifier of the product running in the enclave
    "signerId": "", // hex representation of bytes uniquely identifying the enclave's signer
    "uniqueId": "", // hex representation of bytes uniquely identifying the enclave
    "securityVersion": 1 // uint, security version of the enclave
  } // optional, appears if "isTee" is true, enclave self report information
}

For more information about the information in an enclave self report, see TEE attestation report.

Get a list of linked accounts

Request: GET /accounts

API key: required

Response:

{
  "accounts": [
    {
      "account": "ETH address",
      "proofOfCountry": GeneralProofObject|null,
      "proofOfAge": GeneralProofObject|null
    },
    ...
  ],
  "total": 123 // total number of linked accounts
}

Example:

curl {{zkportal_backend}}/accounts -H 'Authorization: Bearer EXAMPLE_API_KEY'

Get a linked account by Ethereum address

Request: GET /accounts

Query parameters:

  • account - string, Ethereum address

API key: required

Response:

{
  "account": "ETH address",
  "proofOfCountry": GeneralProofObject|null,
  "proofOfAge": GeneralProofObject|null
}

Example:

curl {{zkportal_backend}}/accounts?account=0x646dF754896DCB25590306916de74C103bb7eFBF -H 'Authorization: Bearer EXAMPLE_API_KEY'

Get a list of accounts with proof of country

Request: GET /country_proofs

API key: required

Response:

{
  "accounts": [
    {
      "account": "ETH address",
      "proofOfCountry": GeneralProofObject,
      "proofOfAge": null // is always null, whether the user has a proof of age or not
    },
    ...
  ],
  "total": 123 // total number of accounts with proof of country
}

Example:

curl {{zkportal_backend}}/country_proofs -H 'Authorization: Bearer EXAMPLE_API_KEY'

Get a list of accounts with proof of age

Request: GET /age_proofs

API key: required

Response:

{
  "accounts": [
    {
      "account": "ETH address",
      "proofOfCountry": null, // is always null, whether the user has a proof of country or not
      "proofOfAge": GeneralProofObject
    },
    ...
  ],
  "total": 123 // total number of accounts with proof of age
}

Example:

curl {{zkportal_backend}}/age_proofs -H 'Authorization: Bearer EXAMPLE_API_KEY'

Schema

GeneralProofObject

This object contains a ZK proof with validity and TEE attestation.

{
  "proof": ProofDataObject, // ZK proof object
  "validUntil": 1669597219, // Unix timestamp of the date when the proof expires
  "attestation": AttestationObject // TEE attestation object
}

ProofDataObject

This object contains the data related to the ZK proof, including everything needed to verify the proof

{
  "zkOutput": "", // Base64-encoded bytes of the ZK proof itself
  "proofDetails": ProofOfCountryDetailsObject|ProofOfAgeDetailsObject, // the values used to create a proof, specific to the circuit
  "signature": "", // Base64-encoded bytes of ZK proof's public inputs (publicInputs field) ASN.1 ECDSA signature.
  "publicInputs": "", // Base64-encoded bytes of the ZK proof's public inputs
  "verifyingKey": "" // Base64-encoded bytes of a ZK proof verifying key
}

For details about verifying a proof, see ZK proof section. The public key you get from verifying AttestationObject is the public part of the key, which was used to create signature in this object.

ProofOfCountryDetailsObject

{
  "blacklist": "", // Concatenated list of ISO country codes banned from participating
  "userHash": "" // Base-64 encoded bytes of SHA256 hash over user's country ISO code + random bytes
}

Proof of country

Based on ProofOfCountryDetailsObject, publicInputs in ProofDataObject are the following components concatenated:

  1. Big-endian bits of country blacklist as ASCII characters. Example: USIRRU - 85 83 73 82 82 85, where 85 is 01010101 etc., so the full blacklist is 01010101 01010011 01001001 01010010 01010010 01010101.
  2. Big-endian bits of userHash (see Proof of Country Inputs for details about creating one). Example: 20a8b52013f6dee3aef039c1572aaa220463fe23aadc64ddd8aa3b8c84380bc0 - take 20, which is in hex form - 00100000 as bits - so the full userHash is
00100000 10101000 10110101 00100000 00010011 11110110 11011110 11100011 10101110 11110000 00111001 11000001 01010111 00101010 10101010 00100010 00000100 01100011 11111110 00100011 10101010 11011100 01100100 11011101 11011000 10101010 00111011 10001100 10000100 00111000 00001011 11000000
  1. Concatenate blacklist and user hash bits to get to publicInputs.

Here is a JavaScript snippet you can use for converting bytes (numbers 0-255) to bits

/**
 * @param {Array<number>|Uint8Array} byteArray
 * @return {Array<number>} List of bits in big endian
 */
function bytesToBits(byteArray) {
  const bitArr = Array(byteArray.length * 8);
  let idx = 0;
  for (let i = 0; i < byteArray.length; i++) {
    for (let j = 7; j >= 0; j--) {
      bitArr[idx] = (byteArray[i] >> j) & 0x01;
      idx++;
    }
  }
  return bitArr;
}

ProofOfAgeDetailsObject

{
  "minimumAge": 1670577921, // Unix Timestamp of minimum age (e.g. if the minimum age is 18, then this will be "<Date Of Proof timestamp> - 18 years")
  "userHash": "" // Base-64 encoded bytes of SHA256 hash over user's age timestamp as bytes + random bytes
}

Proof of age

Based on ProofOfAgeDetailsObject, publicInputs in ProofDataObject are the following components concatenated:

  1. Big-endian bits of minimum age Unix timestamp. Example: 1669637350 - 99 132 164 230, where 99 is 01100011 etc., so the full minimum age is 01100011 10000100 10100100 11100110.
  2. Big-endian bits of userHash (see Proof of Age Inputs for details about creating one). Example: d4eb109a529a76c7d964d616fe1f2c780a686c37f034e003a7aa7f7f607fdc75 - take d4, which is in hex form - 11010100 as bits - so the full userHash is
11010100 11101011 00010000 10011010 01010010 10011010 01110110 11000111 11011001 01100100 11010110 00010110 11111110 00011111 00101100 01111000 00001010 01101000 01101100 00110111 11110000 00110100 11100000 00000011 10100111 10101010 01111111 01111111 01100000 01111111 11011100 01110101
  1. Concatenate minimum age and user hash bits to get to publicInputs.

Since timestamp is a number but the circuit takes bits for the minimum age, see the snippet below for converting numbers to bytes. After you get bytes, you can use the snippet in the Proof of country section to convert them to bits.

Here is a JavaScript snippet you can use for converting numbers to bytes

/**
 * @param {number} int Number to convert
 * @param {number} size Byte size of the number to convert, e.g. 4 bytes for numbers 0-4294967295
 * @return {Uint8Array} Array of big endian bytes
 */
function int2ba(int, size) {
  let hexstr = int.toString(16);
  if (hexstr.length % 2) {
    hexstr = '0' + hexstr;
  }
  const ba = [];
  for (let i = 0; i < hexstr.length / 2; i++) {
    ba.push(parseInt(hexstr.slice(2*i, 2*i + 2), 16));
  }
  if (size) {
    const oldlen = ba.length;
    for (let j = 0; j < (size - oldlen); j++) {
      ba.unshift(0);
    }
  }
  return new Uint8Array(ba);
}

AttestationObject

This object contains TEE attestation data, which allows to create a full circle of trust.

{
  "report": "", // Base64-encoded TEE report over a SHA256 hash of ephemeral ECDSA public key used for signing the proof inputs + proof expiry timestamp - hash(key|timestamp)
  "publicKey": "", // Base64-encoded bytes of PKIX, ASN.1 DER form of the ECDSA (curve P256) public key used to sign proof inputs in ProofDataObject
  "signature": "" // Base64-encoded signature for the ciphertext containing the data, which was used to create the proof
}

For details about verifying a TEE report, see SGX report verification example.

Error codes

TODO