How to create your own Bitcoin proof verification page?

Bitcoin proof verification tool

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 -->