Document Certification using Assets
Introduction
This tutorial constitutes a guided walk-through of the Topl development ecosystem explaining one of the major use cases, namely document certification.
Document certification, also known as time-stamping or proof of existence, is one of the most intuitive use cases for blockchain technology beyond digital currencies. Document certification consists in saving a tamper-proof timestamped fingerprint of a document (or other file) on the blockchain. This basically serves as proof that the document existed in a certain version at a certain time and can be used to prove the integrity of the file. That is, you can prove that a document has not been modified since its certification, protecting copyright, sealing log files for supply chains, and any other use case where file integrity is important.
In this tutorial, we will look at how to create a Topl asset transaction that stores and reads document commitments and other data on the blockchain. To do so, we will create a simple transaction that saves a Blake2b-256 of arbitrary data on the blockchain together with metadata. BLAKE hashes uniquely identify sets of data by means of a cryptographic hash function. We will not go into detail on hash functions and cryptography here, but you should know that a Blake2b-256 hash is a 32-byte fingerprint derived from the input data. The reverse operation, i.e. calculating the data from the hash value, is not feasible, so data protection is a welcome side-effect.
Document certification is easy to implement, which means that it is an ideal introduction for explaining Topl concepts and techniques without too much unnecessary complexity.
Prerequisites
This tutorial will assume that you are using our BramblJS library. Please see the Brambl Prerequisites page to make sure that your environment is setup properly.
Learning outcomes
After following this tutorial you will know how to:
- Store and retrieve data on the blockchain
- Use basic functionality of a professional Topl development framework.
- Run your own Bifrost Node
- Use Brambl to connect to your Bifrost Node.
Developing a documentation certificate
Given that you have followed the prerequisites to setup BramblJS, let's create a module to create and interact with our asset with the following code. .
const BramblJS = require("brambljs");
const getSenderKeyManagers = (senders, networkPrefix) {
let keyManagers = [];
if (Array.isArray(senders)) {
for (var i = 0; i < senders.length; i++) {
keyManagers.push(
BramblJS.KeyManager({
networkPrefix: networkPrefix,
password: senders[i][1],
keyPath: senders[i][0]
})
);
}
}
return keyManagers;
}
const signTransaction = async(txObject) {
let obj = {};
return await self.brambljs
.addSigToTx(txObject.messageToSign.result, txObject.keys)
.catch(function(err) {
console.error(err);
obj.error = err.message;
return obj;
});
}
const sendSignedTransaction = async(txObject, brambljs) {
let obj = {};
return new Promise((resolve, reject) => {
let e = brambljs.requests
.broadcastTx({ tx: txObject })
.then(function(result) {
obj.txId = result.result.txId;
resolve(obj);
})
.catch(function(err) {
obj.error = err.message;
reject(err);
});
return e;
});
}
const signAndSendTransaction = async(txObject, brambljs) {
let obj = {};
return signTransaction(txObject).then(function(value) {
if (value.error) {
obj.error = value.error;
return obj;
} else {
return brambljs.sendSignedTransaction(value).then(function(value) {
if (value.error) {
obj.error = value.error;
return obj;
} else {
return value;
}
});
}
});
}
const sendRawAssetTransaction = async(txObject, brambljs) {
let obj = {};
let params = {};
const formattedRecipients = [];
obj.keys = getSenderKeyManagers(
txObject.senders,
private
);
params.minting = true;
params.assetCode = txObject.assetCode;
for (let i = 0; i < txObject.recipients.length; i++) {
const [address, quantity, data, metadata] = txObject.recipients[
i
];
if (data) {
const securityRoot = BramblJS.Hash("string", data);
formattedRecipients.push([
address,
quantity,
securityRoot,
metadata
]);
} else {
formattedRecipients.push([address, quantity]);
}
}
params.recipients = formattedRecipients;
return brambljs.requests
.createRawAssetTransfer(result.params)
.then(function(result) {
obj.messageToSign = result;
return obj;
})
.catch(function(err) {
console.error(err);
obj.error = err.message;
return obj;
});
}
const createAsset = async (args) {
const brambljs = new BramblJS({
networkPrefix: private, // valhalla, toplnet, private
Requests: {
url: "http://localhost:9085", // default port for Bifrost node in private
apiKey: "topl_the_world!" // default Dockerized Bifrost apiKey
}
});
var assetCode = brambljs.createAssetCode("myAssetName");
args.assetCode = assetCode;
return sendRawAssetTransaction(args, brambljs).then(function(value) {
if (value.error) {
// Exception handling here
} else {
return signAndSendTransaction(value, brambljs);
}
});
}
The first part of this code imports the library modules we need, and declares two variables to hold references to the brambljs
instance and the parameters used for the asset transaction.
We initialize the Bifrost node connection in the first part of the function. First of all, we connect to the node:
Requests: {
url: "http://localhost:9085", // default port for Bifrost node in private
apiKey: "topl_the_world!" // default Dockerized Bifrost apiKey
}
Next, in order to create an asset for our certificate, we need to know the address which we will use to mint the asset.
const getSenderKeyManagers = (senders, networkPrefix) {
let keyManagers = [];
if (Array.isArray(senders)) {
for (var i = 0; i < senders.length; i++) {
keyManagers.push(
BramblJS.KeyManager({
networkPrefix: networkPrefix,
password: senders[i][1],
keyPath: senders[i][0]
})
);
}
}
return keyManagers;
}
In the above code, if we know the path of our keyfile for each sender, we can use that to instantiate a KeyManager for our addresses in the code. Under the hood, BramblJS is simply reading the relevant information from a json file stored at the keyfilePath location.
Note:
Remember to replace
txObject.keyfilePath
with the correct path for your local development environment!
const securityRoot = BramblJS.Hash("string", data);
The above code uses the BramblJS library to calculate a Blake2b-256 hash value, which is provided in the Base58 string representation.
Then, we simply iterate through each recipient to which we would like to send an asset, and use this method to create the security root for our data
for (let i = 0; i < result.params.recipients.length; i++) {
const [address, quantity, data, metadata] = params.recipients[
i
];
if (data) {
const securityRoot = BramblJS.Hash("string", data);
formattedRecipients.push([
address,
quantity,
securityRoot,
metadata
]);
} else {
formattedRecipients.push([address, quantity]);
}
}
Now that we have derived the securityRoot and have our keys, it is time to send the parameters to the Bifrost node to get the rawParameters and the messageToSign.
return brambljs.requests
.createRawAssetTransfer(result.params)
.then(function(result) {
obj.messageToSign = result;
return obj;
})
.catch(function(err) {
console.error(err);
obj.error = err.message;
return obj;
});
A few things to note:
1.) createRawAssetTransfer is asynchronous
2.) Calling the Javascript version of createRawAssetTransfer returns a rawTransaction object
3.) createRawAssetTransfer requires aparameters
object as an argument, in which we specify the account/s to use for the senders and other parameters.
The two functions signTransaction
and sendSignedTransaction
are executed sequentially after we have the rawTransaction object from the steps described above.
Once those two functions have executed, we are presented with a response similar to this
{
"jsonrpc": "string"
"id": "string"
"result": {
"txType": "string"
"timestamp": 0
"signatures": {
"aansHqDUHRhD7kztDfQXXZkcGLL4KD8VcEDzQB9fjBPM": "string"
}
"newBoxes": [{
"nonce": "string"
"id": "string"
"evidence": "string"
"type": "string"
"value": {
"type": "string"
"quantity": "string"
"assetCode": "string"
"metadata": "string"
"securityRoot": "string"
}
}
]
"data": "string"
"to": [
[{
"type": "string"
"quantity": "string"
"assetCode": "string"
"metadata": "string"
"securityRoot": "string"
}
]
]
"propositionType": "string"
"from": [
[
"string"
]
]
"minting": true
"txId": "string"
"boxesToRemove": [
"string"
],
"fee": "string"
}
}
We now have to wait a short period for the transaction to be forged (you should be able to confirm this by using the topl_transactionById
RPC method through Postman)
Once the transaction has been forged, you can verify that the output of the transaction is similar to the one that you received after broadcasting the transaction.
Conclusion
In this guide, we have shown the process of developing a Topl certificate based program using document certification as a simple but useful example. We have developed and tested locally in this tutorial, but it is fairly straightforward to deploy it to one of the real Topl networks as well (either Valhalla or the Toplnet).
Updated almost 2 years ago