TypeScript Client Library

Our TypeScript client library provides a user-friendly wrapper around the cronStamp Web API for document timestamping, streamlining integration into your applications. In addition, the cronStamp client library implements document verification, which is performed entirely on the client side and does not require access to our servers.

GitLabAPI referencenpm package

cronStamp Client Library

This is a TypeScript client library for cronStamp, the blockchain-based document timestamping and verification service. For an explanation of cronStamp check out this page.

cronStamp websiteGitLabAPI referencenpm package

Installation

npm

In a Node.js environment you can install the cronStamp client library with npm

sh
npm install @cronstamp/clientlib

Once installed, library functions can be imported like this in your typescript project

typescript
import { requestCertificateForString } from '@cronstamp/clientlib';

...

CDN

If you are in a browser environment instead, you can directly embed the library from a CDN like so

html
<script type="module">
  import { requestCertificateForString } from "https://esm.sh/@cronstamp/clientlib";

  ...
</script>

Usage

The two main functionalities of the client library are document timestamping and document verification. Let's walk through each of the two steps with an example.

Timestamp a document

First comes document timestamping, where we provide the document and receive in turn the certificate. Under the hood, the client library hashes the document and submits the hash to the cronStamp Web API. cronStamp inserts the hash into each supported blockchain and generates a preliminary certificate. From this, the client library assembles and returns the full certificate. Afterwards, the client library performs a full document verification to ensure correctness of the certificate.

Let's dive into an example.

typescript
import { requestCertificateForString } from 'https://esm.sh/@cronstamp/clientlib';

let message = 'This is the data that the certificate will be created for.';

let requestResult = await requestCertificateForString(message, ['xrp'])
  .onMessage((data) => console.log(data.message))
  .start();

You can run the example in this JSFdiddle (open the console in the bottom right to see the output).

For simplicity, the example uses a string (the message variable) to represent the document. Calling requestCertificateForString and passing the message returns the timestamping process which is an object of type RequestProcess. This request process object has several methods, such as configure, onMessage, onError, start, and cancel. For this example, we request only XRP to be included in the certificate by passing 'xrp' as second parameter to requestCertificateForString.

We use onMessage to define handlers for processing the result data of type ResultData which is emitted after each step of the timestamping process (for all possible steps see the Step enum). In this case, we simply print the status message. Calling start starts the timestamping process and returns a promise, which has to be awaited and eventually resolves to the requestResult of type ResultData. Calling onMessage is optional and in case of it's absence the entire ResultData object is logged to console for each process step.

After successful execution of th example, the requestResult object looks as follows with the final certificate in the currentCert field. The step is set to 7 corresponding to Step.REQUEST_FULL_CERTIFICATE_SUCCEEDED.

typescript
{
  step: 7,
  type: 0,
  currentCert: {
    blockchains: {
      xrp: {
        transaction: '9851AFC28E106CAC9228B597EADEF20985647BF57F937404BCC0C36C834D21AB', 
        block_timestamp: 786491741, 
        merkle_tree_splice: [
          'vUAUzj3hnMufac7DkXD4RYYC/nRngagSF6Gnmbjl8A0=',
          'Y5NWzOO3tXA1lAjfF1LFb747lrqK1BNd/iEu0ADQqPw=',
          'dsGP+cPO/twnKNamRIuVs88KF21QAzhMtRs5/CMXiFo='
        ]
      }
    },
    meta: {
      version: 1,
      blockchains: ['xrp'],
      salt: '670b53ed2eb453e946c197b6be157745'
    },
    info: {
      hash_algorithm: 'SHA-256',
      salted_hash: '3XBMLhyb5dZedq4mx6HdcS9hWrt19isIigov9WHtLdE='
    },
    document_hash: 'R/IRPRqKv8VS4CGemNZDJubr78cc7WA8J4O2h1K7R6Y='
  },
  message: 'Certificate created and verified successfully!',
  additionalDataType: 0,
  inputData: {
    type: 0,
    data: 'This is the data that the certificate will be created for.',
    blockchainsToRequire: ['xrp'],
    certFileName: 'This is the data tha',
    displayName: 'This is the dat[...]'
  },
  currentTime: '2024-11-08T20:34:27.327Z'
}

In addition to requestCertificateForString, the client library also provides a method requestCertificateForFile which takes a file, and requestCertificateForHash which takes a hash, in case you want to hash the document on your own. There is also the generic method requestCertificate which works for all three types of inputs.

Verify a document

Document verification requires as inputs the document and the certificate and returns whether the document has been modified since the timestamping or not. To this end, the client library hashes the document (together with the hashes in the merkletree splice) and compares the resulting Merkle tree root hash with the one stored on each blockchain. If the root hashes match for all blockchains, the document is verified, i.e., has not been modified. Document verification takes place on client-side only and requires no connection to the cronStamp servers.

Let's see an example of how document verification with the client library works. You can also find the example in this JSFiddle.

typescript
import { Step, verifyCertificateForString } from 'https://esm.sh/@cronstamp/clientlib';

let message = 'This is the data that the certificate will be created for.';

let certificate = {
  blockchains: {
    xrp: {
      transaction: 'B0479FE372F06F1BBE5E0AD547D9A92A67A211E2A6FEC832EF9710CE42BA783F',
      block_timestamp: 786501102,
      merkle_tree_splice: [
        'aqQGaOTkjQ7sn2l1EhWypyY7pg/nwil+oKYBR+mrMvU=',
        '',
        'rKpRM19PEdnKcgm2FuADP9dKptszNFBweCtgvsdICCM='
      ]
    }
  },
  meta: {
    version: 1,
    blockchains: ['xrp'],
    salt: '7174d145aa1f57d50b38cb94d70533ce'
  },
  info: {
    hash_algorithm: 'SHA-256',
    salted_hash: 'GfGTduOe5dpC3JL4RY1NnnmZUFwREQWkE2Tzw45N6jk='
  },
  document_hash: '2xXmxEInHGanIixLeS28kOBomIYWuuFKWsFnceNwRQI='
};

let verificationResult = await verifyCertificateForString(certificate, message)
  .onMessage((data) => console.log(data.message))
  .start();

The document is again represented by the string in the message variable. The certificate is defined as instance of the Certificate type. Both message and certificate are passed to verifyCertificateForString, which returns a VerificationProcess instance. The VerificationProcess has the same methods as the RequestProcess, i.e., configure, onMessage, onError, start, and cancel. Here, we define again a handler for logging the status message and start the verification process by calling start.

When executing the example the last two status messages in the console indicate that the document is verified successfully, i.e., has not been modified since timestamping.

console
...
The certificate is valid! The hash in the blockchain and certificate match. This means the document existed at time 2024-11-08T20:34:21.000Z
Verified root hash successfully in 1 blockchains.

The verificationResult object looks like this with step 10 corresponding to Step.VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED.

typescript
{
  step: 10,
  type: 0,
  currentCert: { ... },
  message: 'Verified root hash successfully in 1 blockchains.',
  additionalDataType: 6,
  additionalData: 1731098061000,
  inputData: {
    type: 0,
    data: 'This is the data that the certificate will be created for.',
    certFileName: 'This is the data tha',
    displayName: 'This is the dat[...]'
  },
  currentTime: '2024-11-08T20:36:59.643Z'
}

Note the additionalData field that holds additional information specific to the step, in this case the unix timestamp of the blockchain block containing the document hash. What type of information is present in the additionalData field is indicated by the additionalDataType field that takes a value of the AdditionalDataType enum. Here, the value is 6 corresponding to AdditionalDataType.TIMESTAMP.

You can use the value in the step field of the verificationResult object like this to determine whether verification was successful.

typescript
if (verificationResult.step == Step.VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED) {
  console.log(`Document verified successfully.`);
} else {
  console.log(`Error during certificate verification: ${verificationResult.message}`);
}

Check out this JSFiddle to see what happens if you run verification on a modified document.

The last line of console output is "Error during certificate verification: Locally calculated data hash does not match hash in provided certificate: 9OI5T+j0wCi9Fp31xTBbkUkY3sguo4b69RBAngP0KYM= != 2xXmxEInHGanIixLeS28kOBomIYWuuFKWsFnceNwRQI=" indicating a mismatch of the calculated document hash and the hash stored in the blockchain. The client library correctly detects that the document has been modified.

Customize error handling

Similar to onMessage, the RequestProcess and VerificationProcess objects returned by requestCertificate and verifyCertificate have an optional onError method. With this you can define a custom handler for processing the ResultData object emitted in case of an error. If onError is not called, the handler defined by onMessage is used as a fallback in case of an error.

Customize configuration

You can customize the default settings of the client library by calling configure on the RequestProces or VerificationProcess with a configuration object as shown in the example below. Note that all values in the configuration object are optional, i.e., you could also provide a partial configuration to update only a subset of the settings.

typescript
import { requestCertificateForString, CronStampConfig, LOG_LEVELS } from '@cronstamp/clientlib';

export const customConfig: CronStampConfig = {
  LOG_LEVEL: LOG_LEVELS.WARN,
  BLOCKCHAIN_XRP_SERVERS: ['wss://testnet.xrpl-labs.com'],
  BLOCKCHAIN_SOLANA_SERVERS: ['https://api.devnet.solana.com'],
  API_URL: 'https://api.cronstamp.com',
  CERT_REQUEST_TIMEOUT: 100,
  CERT_REQUEST_INTERVAL: 3
};

let requestResult = await requestCertificateForString('some message').configure(customConfig).start();

The default settings can be found here.