Platform Proofs

Since data verification is a critical aspect of Dash Platform, all Platform endpoints can provide an optional proof that the response is correct. Set the optional prove parameter ("prove": true) in the request to receive a proof that contains the requested data.

Proof Structure

Each proof consists of four parts:

FieldTypeDescription
rootTreeProofBytes (base64)Merkle path to the storeTreeProof
storeTreeProofBytes (base64)Merk tree proof containing the store root hash, the merkle path, and the requested data
signatureLlmqHashBytes (base64)Hash of the LLMQ that created the signature
signatureBytes (base64)Signature of the merkle root of the rootTreeProof
{
  "proof": {
    "rootTreeProof": "AQAAAANDyOH1e+nnMfZAiT9W6O8bnSwNt7ExdoPORAjOOQl1hncTjwAKTjkBt9itDASm0uhlmZTIAE7qaorXXpW9W4TMJu/rY51+fQEeMotzZc6ndsHCaWC3rvrAWkzYni1iBYgBBQ==",
    "storeTreeProof": "Aa3xeZMhgUlpOmObZgsBc/A7Tw8DiE7Al0d8UnlPKDxEAkxb4mgZiSCI/Dww08L/Kypsf9zOSuQ9pbuCf1+iLKo2EAFJN2x17KAW3m6kfE4Vbv2sI5P9cRgajTYzLrYc41t5VRE=",
    "signatureLlmqHash": "AAABDRGoCpOki/CVlNkMeQhapUFyIoXOESFPn1cXK6I=",
    "signature": "AU+FZUCH/OKrM8GuNo0L8S+mKCpbAe/f9SJ1V3fhudrlOja1KislQRcBbIShk74bGLvN8exT/6BVxPHQtaM+LDqfuZGXG1EmgNGY2827/Pyn8XGtP2jDmuhvonCRD4QN"
  },
  "metadata": {
    "height": 433,
    "coreChainLockedHeight": 9801
  }
}

Root tree proof

📘

Details regarding the root tree proofs and their verification will be provided in a future update to this page.

Store tree proof

Store tree proofs are based on a modified version of Merk. Some details from the Merk documentation are included below. Additional details are available in the Algorithms document on the Merk repository.

Structure

Merk proofs are a list of stack-based operators and node data, with 3 possible operators: Push(node), Parent, and Child. A stream of these operators can be processed by a verifier in order to reconstruct a sparse representation of part of the tree, in a way where the data can be verified against a known root hash.

The value of node in a Push operation can be one of three types:

  • Hash(hash) - The hash of a node
  • KVHash(hash) - The key/value hash of a node
  • KV(key, value) - The key and value of a node

Binary Format

We can efficiently encode these proofs by encoding each operator as follows:

OperatorOp. ValueSizeDescription
Push(Hash(hash))0x0132 bytesNode hash
Push(KVHash(hash))0x0232 bytesNode key/value hash
Push(KV(key, value))0x03< 1-byte key length >
< n-byte key >
< 2-byte value length >
< n-byte value >
Node key/value

This results in a compact binary representation, with a very small space overhead (roughly 2 bytes per node in the proof (1 byte for the Push operator type flag, and 1 byte for a Parent or Child operator), plus 3 bytes per key/value pair (1 byte for the key length, and 2 bytes for the value length)).

Retrieving response data from proofs

The function below shows a simple example of parsing a response's storeTreeProof to retrieve the data asked for by the request:

// Get data from base64 encoded store tree proof
function getStoreProofData(storeProof) {
  const buf = Buffer.from(storeProof, 'base64');

  let x = 0;
  let valueFound = false;
  while (x < buf.length) {
    const type = buf.readUInt8(x);
    x += 1;

    switch (type) {
      case 0x01: { // Hash
        x += hashLength;
        break;
      }

      case 0x02: { // Key/value hash
        x += hashLength;
        break;
      }

      case 0x03: { // Key / Value
        const keySize = buf.readUInt8(x);
        x += 1;
        x += keySize;

        const valueSize = buf.readUInt16BE(x);
        x += 2;

        // Value
        const value = buf.toString('hex', x, x + valueSize);
        x += valueSize;
        const map = cbor.decode(value);

        valueFound = true;
        return map;
      }

      case 0x10: // Parent
        break;

      case 0x11: // Child
        break;

      default:
        console.log(`Unknown type: ${type.toString(16)}`);
        break;
    }
  }
  console.log(`Value found: ${valueFound}`);
}

Did this page help you?