A more sophisticated example

Using the SDK against a database

Let's look at a more complex example. In this case, we'll extract some data from a PostgreSQL database and anchor it to the blockchain.

Setup

We'll need the provendb-sdk-node and pg libraries as well as fs:

const fs = require("fs");
const {
    Client
} = require("pg");
const {
    merkle,
    anchor
} = require('provendb-sdk-node');

Now we connect to the ProvenDB anchor service and to the PosgreSQL database:

async function main() {
    try {
        if (!('anchorToken' in process.env)) {
            console.error('Must specify anchor token in `anchorToken` environment variable');
            process.exit(1);
        }
        const anchorToken = process.env['anchorToken'];
        const myAnchor = anchor.connect(anchor.withAddress('anchor.proofable.io:443'), anchor.withCredentials(anchorToken));
        const pgClient = new Client({database: 'provendb'});
        await pgClient.connect();

Note that the anchor key is supplied via an environment variable. See Obtaining an API key for more details.

Getting the data

We extract the data from the PostgreSQL database that we want to anchor as follows:

  const query = `SELECT contractid,metadata,contractData 
                         FROM contracts ORDER BY contractid 
                        LIMIT 10`;
  const data = await pgClient.query(query);

Next, we create a Merkle tree from the data returned by PostgreSQL:

const builder = new merkle.Builder('sha-256');
        const keyValues = [];
        data.rows.forEach(row => {
            const key = row['contractid']; 
            keyValues.push({
                key,
                value: Buffer.from(JSON.stringify(row))
            });
        });
builder.addBatch(keyValues);
const tree = builder.build();

Key values are constructed with the primary key of each row as the key and a buffer from the concatenation of all the rows columns as the value. The "value" will be hashed as the tree is created.

Anchor the data

Now we anchor that proof to a public blockchain - in this case HEDERA (note that the HEDERA constant anchors to the Hedera testnet. To anchor to the mainnet, use HEDERA_MAINNET):

        const anchoredProof = await myAnchor.submitProof(tree.getRoot(),
            anchor.submitProofWithAnchorType(anchor.Anchor.Type.HEDERA),
            anchor.submitProofWithAwaitConfirmed(true));
        tree.addProof(anchoredProof);
        fs.writeFileSync('treeWithProof.json',JSON.stringify(tree));

The Proof format

treeWithProof.json contains the Merkle tree as well as the chainpoint document showing how the tree is anchored to the blockchain. Here's an abridged version of that file:

{
    "algorithm": "sha-256",
    "layers": [
        [
            "1:800a2c26fa2b3c536a378fffee69d5d9afcc818c8fb207799730380dc4929577",
            "2:e8db5dbc03250b53a67c72baa6da9012577f9066bf4acb030068d30552a29d63",
        <snip>
        ],
       <snip>
        [
            "af938babed6ff9bf2818ba62327cdbc76cea0398a040de613d469cdceae8367d"
        ]
    ],
    "proofs": [
        {
            "id": "af938babed6ff9bf2818ba62327cdbc76cea0398a040de613d469cdceae8367d:Zk5-reiStsvej1zgeY802",
            "anchorType": "HEDERA",
            "format": "CHP_PATH",
            "batchId": "Zk5-reiStsvej1zgeY802",
            "hash": "af938babed6ff9bf2818ba62327cdbc76cea0398a040de613d469cdceae8367d",
            "status": "CONFIRMED",
            "metadata": {
                "txnId": "73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09",
                "txnUri": "https://testnet.dragonglass.me/hedera/search?q=73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09",
                "blockTime": 1627346908,
                "blockTimeNano": 393228000,
                "validStart": "2021-07-27T00:48:19.302714114Z",
                "operator": "0.0.32921",
                "transactionFee": 53724,
                "confirmedByMirror": true
            },
            "data": {
                "@context": "https://w3id.org/chainpoint/v3",
                "type": "Chainpoint",
                "hash": "af938babed6ff9bf2818ba62327cdbc76cea0398a040de613d469cdceae8367d",
                "hash_id_node": "da023c5c-c895-11e9-a32f-2a2ae2dbcce4",
                "hash_submitted_node_at": "2021-07-27T00:48:28Z",
                "hash_id_core": "da023c5c-c895-11e9-a32f-2a2ae2dbcce4",
                "hash_submitted_core_at": "2021-07-27T00:48:28Z",
                "branches": [
                    {
                        "label": "pdb_hedera_anchor_branch",
                        "ops": [
                            {
                                "anchors": [
                                    {
                                        "type": "cal",
                                        "anchor_id": "73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09",
                                        "uris": [
                                            "https://anchor.proofable.io/verify/hedera/73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09"
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        }
    ],
    "nodes": 11
}

The first section of the proof shows the tree itself, starting with the key and hashed value pairs, and going up to the tree until we find the root hash for the tree. The Proof section shows how that root hash is anchored to the blockchain. See Document Proofs for more information about proof formats.

Creating a single-key proof

This sort of proof can be used to assert the integrity of the complete set of data that was included in the tree. However, what we often want to do is prove the integrity of a single document. To do this we create a singleRow proof - sometimes called a "path proof".

Here we create such a proof for the element in the tree with the key value of "1" (eg CONTRACTID=1):

        let singleKeyProof=tree.addPathToProof(anchoredProof,"1","proof");
       
        fs.writeFileSync('singleKeyProof.json',JSON.stringify(singleKeyProof));

The Single Key proof looks like this (abridged):

{
    "id": "af938babed6ff9bf2818ba62327cdbc76cea0398a040de613d469cdceae8367d:Zk5-reiStsvej1zgeY802",
    "anchorType": "HEDERA",
    "batchId": "Zk5-reiStsvej1zgeY802",
    "status": "CONFIRMED",
    "hash": "800a2c26fa2b3c536a378fffee69d5d9afcc818c8fb207799730380dc4929577",
    "format": "CHP_PATH",
    "metadata": {
        "txnId": "73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09",
        "txnUri": "https://testnet.dragonglass.me/hedera/search?q=73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09",
        "confirmedByMirror": true
    },
    "data": {
        "@context": "https://w3id.org/chainpoint/v3",
        "type": "Chainpoint",
        "hash": "800a2c26fa2b3c536a378fffee69d5d9afcc818c8fb207799730380dc4929577",
        "hash_id_node": "da023c5c-c895-11e9-a32f-2a2ae2dbcce4",
        "hash_submitted_node_at": "2021-07-27T00:48:28Z",
        "hash_id_core": "da023c5c-c895-11e9-a32f-2a2ae2dbcce4",
        "hash_submitted_core_at": "2021-07-27T00:48:28Z",
        "branches": [{
            "label": "proof",
            "ops": [{
                "r": "e8db5dbc03250b53a67c72baa6da9012577f9066bf4acb030068d30552a29d63"
            }, {
                "op": "sha-256"
            }, {
                "r": "ce9ce973fd35c04467b0b170da1bb58fdf8bfa0aa2b48fcd492f588197d3ec11"
            }, {
                "op": "sha-256"
            }, {
                "r": "7d896c2aff6943ab86bcde5b8b561c88f9e440311af15fa2134d6a1bb25b33be"
            }, {
                "op": "sha-256"
            }, {
                "r": "8d5b3bfbd4027daa00a4ba2866246257c6c1ff1b582e6223413c6a6bc72356f1"
            }, {
                "op": "sha-256"
            }],
            "branches": [{
                "label": "pdb_hedera_anchor_branch",
                "ops": [{
                    "anchors": [{
                        "type": "cal",
                        "anchor_id": "73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09",
                        "uris": ["https://anchor.proofable.io/verify/hedera/73c4f342c111b617158c48ede4ed1b4ef59a9b0d9f1f36a091a47508c889967e96c2b983ad4a5b79548d1da1d3084b09"]
                    }]
                }]
            }]
        }]
    }
}

This proof, in the Chainpoint format, shows the sequence of hashes that link the individual row to the hash on the blockchain. Using this proof you can prove definitively the provenance and integrity of an individual row.

Validating the proof

Our assumption in this example is that the data being stored to the database is immutable - it won't be subject to any updates. To validate that no tampering of the data has occurred, we can use the validate method.

To validate data against an existing proof, we create new tree containing the current state of the data:

   const newData = await pgClient.query(query);
        // Build a merkle tree from the query results
        console.log('Validating data');
        const newBuilder = new merkle.Builder('sha-256');
        keyValues = [];
        newData.rows.forEach(row => {
            const key = row['contractid'];
            keyValues.push({
                key,
                value: Buffer.from(JSON.stringify(row))
            });
        });
        newBuilder.addBatch(keyValues);
        const newTree = newBuilder.build();
        // Validate the new data against the proof 
        let validation = await newTree.validateProof(anchoredProof);
        console.log('Validation result: ', validation);