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);
Updated about 3 years ago