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
storeTreeProofObjectObject containing data and proofs from one or more store trees. Currently there are 5 types of trees: identities, public key hash to identity IDs, data contracts, documents, and state transitions. The merk tree proofs contain 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": "v+99FytmaUPDP65HthQllBL1JDXt2Zu/kzFEQRw66rT6QF8LYwKmAP6fEaXLaSVPe/OHfTDEG2+KoLxjyirQIDmDy4lNl4yhJE5stQZGO2G/74H4MxN/a/luSWkqE1vF",
    "storeTreeProofs": {
      "identitiesProof": "AeFWk/kp1HXlJMzA4Wwov+NifjrocHebDU8863BDtp4aAlHeCG7lcVi52OSo+U5LlykSjARXJ5Rv6hE+mui+RnUFEAGJ24nuRAkAZMjcRp1sbzLPwYxxagTD12YTLksNN+y1cAKxpoxQdHpC/RQN7cq7fE/z6+0ccpoVQbobRPGtfCSj4hABjDZM5byc2NbfTNb7NGWDKm0bAoZjaAHx+C7Gn2cKmfsCIyWhRVW4QDdnoxTDvJuHCKJeK8dWzsrfVuUYTejcw6MQAX4HE7Y1WuXPPAG6uU9vewbilQnhjLzSTYNVLsdkxmLfAmwhTWaLg5A+tuxnzvdPhNU+bmbyMHr4PIL8Z+ScbyikEAFtCRA34Yl9tuEzqAF1EdiY0U9/jNoyEpU2vkPLO7xUYAJEmc3z/snpNWPXQdMrrAAHqWNhwddPRSMrF0epC75qThADIHwTzudaE/98V8XvldeYDIpe0yZOW3s6iK0jdqsoOJz3AIABAAAApGJpZFggfBPO51oT/3xXxe+V15gMil7TJk5bezqIrSN2qyg4nPdnYmFsYW5jZRoAp8O4aHJldmlzaW9uAGpwdWJsaWNLZXlzgaNiaWQAZGRhdGFYIQOF51gOnYk5T+0EdR4DSKUkDo5TmEDMoMxdxOy7FnqKjmR0eXBlABEREQL0H2yuZyiMzKzHmrXCwp/W7DuDkZYlEx7JE5xlYGhJxhABJt9KcGPXHnE7hzz3aQ9PpYDhvILZCDUOu5BBwV66RPYRERECyX/3Cih/TZdB9cVOX8Xmo2UEPNvt9iOufQ4oCmoytwsQAU14wPdQ7t7FfsfXx9fGnwbZk8h1uxoWd0MroZRO0YVXEQ==",
      "publicKeyHashesToIdentityIdsProof": "Afe33zbtlgXiPzJ1+zSjjVttIBmiKHy1iEc7uOKqxVUGAjs/C8gAlTwbVhnRBqbhGFkz0Kg/0Cr8mAV41WXxocBpEAGVsw8werVp7Cka+OSMj3GgkX2Da0FMMGGIJx4aZxPwPhE="
    },
    "signatureLlmqHash": "AAACBMSv9TakRGNdP+yvxw/+VCgIbALhn314jLOpgcY=",
    "signature": "Fhl8Md9MDlB0Tlekgjoj+Qe5PdKeUDyL6svVmcP9ttRu1UB7oeAGaSMAyqJI+k/HA/jAfPFb9+q9gepdZDhj8zHrl5BRSaAiBPEtM6CTQ+eCWUvqOlDENVQfubrXLLdk"
  },
  "metadata": {
    "height": "7986",
    "coreChainLockedHeight": 57585
  }
}

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.

Dash Platform 0.21.0 introduced updates to support returning multiple store tree proofs. Each response that requests proofs will receive one or more of the following:

  • identitiesProof
  • publicKeyHashesToIdentityIdsProof
  • dataContractsProof
  • documentsProof
  • stateTransitionProof

🚧

Dash Platform 0.21.0 introduced a 4 byte protocol version that is prepended to the binary format and is not part of the CBOR-encoded data. When parsing proofs it is necessary to exclude these bytes before decoding the returned data with CBOR.

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 values = [];
  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 + keySize);

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

        // Value
        // Start at x+4 because the first 4 bytes are the protocol version
        // and are not part of the CBOR value
        const value = buf.toString('hex', x + 4, x + valueSize);
        x += valueSize;
        const map = cbor.decode(value);

        valueFound = true;
        values.push(map);
        break;
      }

      case 0x10: // Parent
        break;

      case 0x11: // Child
        break;

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

Did this page help you?