This article will explain how to create your own web page to verify the proofs of existence and the proofs of signature timestamped on Bitcoin using the Woleet Web libraries. These libraries are independent of Woleet except the call to the Woleet API to get the proofs receipts.
To see a live example you can go to https://btcproof.info/.
You have two ways to implement this page:
- Use the Woleet widget.
- Implement your own solution.
With the Woleet widget
Installing Woleet widget
npm i woleet-widget
Directly in your page
Install the differents dependencies:
<link href="./node_modules/@woleet/woleet-widget/dist/style.css" rel="stylesheet">
<script src="./node_modules/@woleet/woleet-widget/dist/woleet-widget.js">
</script>
<script src="./node_modules/@woleet/woleet-weblibs/dist/woleet-weblibs.js">
</script>
And just put the widget via the class woleet-widget
:
<div class="woleet-widget"></div>
You can initialize this widget with a pre-calculated hash:
<div class="woleet-widget" data-hash="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"></div>
Custom UI with Woleet weblibs
Installing Woleet Web libs
You can use npm to add Woleet web libraries to your project by running:
npm i @woleet/woleet-weblibs --save
Directly in your page
<script src="node_modules/@woleet/woleet-weblibs/dist/woleet-weblibs.min.js">
</script>
Note : To be able to perform hash on files larger than 500MB, woleet-hashfile-worker.min.js
and woleet-crypto.min.js
must be accessible (i.e. in the same directory). If woleet-hashfile-worker.min.js
is not on the same directory you have to indicate the path.
├── foo
│ ├── worker.min.js # woleet-hashfile-worker
│ └── woleet-crypto.min.js # must NOT be renamed
├── bar
│ └── woleet-weblibs.min.js
└── index.html:
- <script>woleet = { workerScriptPath: "/foo/worker.min.js" }</script>
- <script src="/bar/woleet-weblibs.min.js"></script>
Verify a file (without a receipt)
woleet.verify.WoleetDAB(file) or woleet.verify.WoleetDAB(hash)
Returned promise (can be empty):
{
code: {'verified' | error message} receipt verifcation status code
timestamp: {Date} proven timestamp of the data (for a data anchor) or of the signature (for a signature anchor)
confirmations: {Number} number of confirmations of the block containing the transaction
receipt: {Receipt} proof receipt
identityVerificationStatus:
{
code: {'verified' | 'http_error' | 'invalid_signature'} identity verifcation status code
identity: [Identity object]
}
}
The identity object is a set of X.500 attributes:
{
"commonName": "John Smith",
"organizationalUnit": "Production",
"organization": "Woleet SAS",
"locality": "Rennes",
"country": "FR"
}
Example:
window.onload = function () {
let r = document.getElementById('result');
let _file = null;
let _receipt = null;
window.setFile = function (file) {
r.innerText = '';
_file = file;
check();
};
window.setReceipt = function (receipt) {
r.innerText = '';
let reader = new FileReader();
reader.onloadend = function (e) {
try {
_receipt = JSON.parse(e.target.result);
check();
} catch (err) {
r.innerText = 'Error while parsing receipt, is it a JSON file?';
}
};
reader.readAsText(receipt);
};
let check = function () {
if (_file && _receipt) {
r.innerText = 'pending ...';
woleet.verify.DAB(_file, _receipt)
.then(function (result) {
if (result.code !== 'verified') {
r.innerText = 'Error: ' + result.code;
} else {
r.innerText = 'The file matches the receipt and the blockchain confirmed it '
+ result.confirmations
+ ' times on '
+ result.timestamp.toISOString();
if (_receipt.signature) {
r.innerText += ', it has been signed by ' + _receipt.signature.pubKey;
if (result.identityVerificationStatus) {
if (result.identityVerificationStatus.code !== 'verified')
r.innerText += ' (but identity cannot be verified using ' + _receipt.signature.identityURL + ')';
else
r.innerText += ' (identity verified using ' + _receipt.signature.identityURL + ')';
}
}
}
}, function (error) {
console.error(error);
r.innerText = 'Error: ' + error.message;
})
}
}
}
Verify a file (with a proof receipt)
woleet.verify.DAB(file, receipt)
or
woleet.verify.DAB(hash, receipt)
The receipt is in the Chainpoint format.
The return format matches the verification function without a receipt return format.
Other usage
The receipt can be independently tested:
woleet.verify.receipt(receipt)
Hasher usage exemple:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hash file(s) demo</title>
<!-- IE ZONE -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<script type="text/javascript">
if (/MSIE \d|Trident.*rv:/.test(navigator.userAgent))
document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.5/bluebird.min.js"><\/script>');
</script>
<!-- END IE ZONE -->
<!-- Woleet web libs (regular version) -->
<script>woleet = {workerScriptPath: '../dist/woleet-hashfile-worker.js'}</script>
<script src="../dist/woleet-weblibs.js"></script>
<!-- Woleet web libs (minified version) -->
<!--<script src="../dist/woleet-weblibs.min.js"></script>-->
<script>
window.onload = function () {
// Instantiate a new file hasher
let hasher = new woleet.file.Hasher;
let li = null;
window.hasher = hasher;
window.start = function (files) {
// This transformation is for testing purpose only
// we could pass directly "files" as it is a FileList object
let arr = [];
for (let i = 0; i < files.length; i++) arr.push(files[i]);
try {
hasher.start(arr);
} catch (err) {
console.warn(err);
}
};
hasher.on('start', function (message) {
li = document.createElement("li");
document.getElementById('hashes').appendChild(li);
li.innerText = message.file.name + ': started';
});
hasher.on('progress', function (message) {
li.innerText = message.file.name + ': ' + (message.progress * 100).toFixed(2) + '%';
});
hasher.on('error', function (message) {
if (!li) {
li = document.createElement("li");
document.getElementById('hashes').appendChild(li);
}
li.style.color = 'red';
console.error(message);
li.innerText = message.file.name + ': ' + (message.error);
li = null;
});
hasher.on('cancel', function (message) {
li.innerText = message.file.name + ': cancelled';
li = null;
});
hasher.on('skip', function (message) {
li.innerText = message.file.name + ': skipped';
li = null;
});
// On success, display computed hash
hasher.on('result', function (message) {
li.innerText = message.file.name + ': ' + message.result;
li = null;
});
}
</script>
</head>
<body>
<h3>Hash file(s) demo</h3>
<input type="file" id="input" multiple onchange="start(this.files)">
<button onclick="if (!hasher.isReady()) hasher.cancel()">Cancel</button>
<button onclick="if (!hasher.isReady()) hasher.skip()">Skip</button>
<ul id="hashes"></ul>
</body>
</html>
For more advanced usage:
Go to the Woleet Web libraries documentation.
Limitations
– Proof format
The receipt object matches the Chainpoint format version 1 and 2, and the Woleet Chainpoint format (i.e. classic format with a “signature” field).
Woleet Chainpoint 1 example:
{
"header": {
"chainpoint_version": "1.0",
"merkle_root": "76280be77b005ee3a4e61a3301717289362e1a9106343c7afba21b55be33b39b",
"tx_id": "01b321351b6a1dd315e08d5613c68c2cafc36e76239b9c3f3aced5e72194bded",
"hash_type": "SHA-256",
"timestamp": 1497625706
},
"signature": {
"signature": "HxVxyhfiJ1EyEDlhXidshWs3QQxb3JUcAvKpt1NLMonLXWWKXL39OLH3XXGofTho5JKjrZUY32sRoX6g2mh/Os0=",
"signedHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"pubKey": "19itkAbBMnjpC8xL4nHWWebgANEGUS2coQ"
},
"target": {
"target_hash": "626484929addc065a418b5a036642f30f6995945c3c75c7003c1ce2779d96a6b",
"target_proof": [
{
"parent": "568cf14e36229a6b81fa19b49c46a4ab36629d154572151af869619c225fa289",
"left": "626484929addc065a418b5a036642f30f6995945c3c75c7003c1ce2779d96a6b",
"right": "7bf003add22b5472106cbd92467dd09b1bafd2c14b53337c8f5f0cb3b73d8712"
},
{
"parent": "1b79f991a650f97ca1f6e391aa5850894b9d8c4c3151c1c4ee58dc1429abe478",
"left": "2d626ed118e1d84929f5977f8c4eb1cfb77459a8d6ea4b141e7e8651dcb48e5c",
"right": "568cf14e36229a6b81fa19b49c46a4ab36629d154572151af869619c225fa289"
},
{
"parent": "229f9863f84f095584b6b1f043b59b51934666d0100475c8f814455b2f87d3e8",
"left": "1b79f991a650f97ca1f6e391aa5850894b9d8c4c3151c1c4ee58dc1429abe478",
"right": "03926260dcb98d387fe560e6032ce012938cd18cddc25283a01de8ecef98feb3"
},
{
"parent": "76280be77b005ee3a4e61a3301717289362e1a9106343c7afba21b55be33b39b",
"left": "229f9863f84f095584b6b1f043b59b51934666d0100475c8f814455b2f87d3e8",
"right": "8c6d7d8fa5a4418d79ef9699af0a58bc63a43f603e0f41533b743ba656a76fcf"
}
]
},
"extra": [
{
"size": "0"
},
{
"anchorid": "561920c1-68a0-468e-82c9-982e7c4b1c63"
}
]
}
– Browser
These libraries have been tested on all modern web browsers and should work on any browser supporting Promises and Workers (note that if Workers are not supported, it is still possible to hash files whose size do not exceed 50MB).
Internet explorer
Since Internet Explorer 11 does not fully support promises, you will have to include a third party library such as bluebird:
<!-- IE ZONE -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<script type="text/javascript">
if (/MSIE \d|Trident.*rv:/.test(navigator.userAgent))
document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.5/bluebird.min.js"><\/script>');
</script>
<!-- END IE ZONE -->