@cronstamp/clientlib
v0.2.0
Published
Client library for cronstamp, a blockchain-based document timestamping and verification service.
Downloads
99
Readme
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 website — GitLab — API reference — npm package
Installation
npm
In a Node.js environment you can install the cronStamp client library with npm
npm install @cronstamp/clientlib
Once installed, library functions can be imported like this in your typescript project
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
<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 HTTP API. cronStamp inserts the hash into each supported blockchain and generates a partial 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 the full example.
import { requestCertificateForString } from '@cronstamp/clientlib';
let message = 'This is the data that the certificate will be created for.';
let requestResult = await requestCertificateForString(message)
.onMessage((data) => console.log(data.message))
.start();
You can run the example in this JSFdiddle.
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
.
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_FULLCERT_SUCCESS
.
{
step: 7,
type: 0,
currentCert: {
version: 1,
hash_algorithm: 'SHA-256',
blockchains: {
xrp: {
transaction: '0E128CBFD3FA4E6D9BA6C8061CBFD3C56D4973A89CBF98C5B77465FA805B68F6',
block_timestamp: 777154992
}
},
merkle_tree_splice: [],
is_merkle_tree: false,
document_hash: '2xXmxEInHGanIixLeS28kOBomIYWuuFKWsFnceNwRQI=',
salt: '1b60e79985d45c7d853d52c8238092ab',
salted_hash: 'TxwxZNDPRqbhgQebukA49aSsw+X+IIt+hOi7X2RUbh0='
},
message: 'Certificate created and verified successfully!',
additionalDataType: 0,
additionalData: undefined,
inputData: {
type: 0,
data: 'This is the data that the certificate will be created for.',
is_merkle_tree: false,
certFileName: 'This is the data tha',
displayName: 'This is the dat[...]'
},
currentTime: 2024-08-16T20:23:17.141Z
}
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 this hash with the one stored on each blockchain. If the 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.
import { type Certificate, Step, verifyCertificateForString } from '@cronstamp/clientlib';
let message = 'This is the data that the certificate will be created for.';
let certificate: Certificate = {
version: 1,
hash_algorithm: 'SHA-256',
blockchains: {
xrp: {
transaction: '8076BF4F96DE57EF64B151FC28C78A66BD2FA29E88D22B923A6775D4B383A893',
block_timestamp: 777221110
}
},
merkle_tree_splice: [
'VB6i9EGb5v8e5+8klTyPofAr60PFJMZGEDW0u1/eH2Y=',
'gJAjot3ZI113UvuanaBBznWRJqpTLpztOWhImxZx0v8=',
'FzmiHQphqpqnbxkA5aykX8gt2Q55Gtp2AAbNUrAAPDw=',
'sj8Sx5llDnx/36qJogZ8YwX1A9LpVEY2DwJYBschke8=',
'6zJvCdGG5iLggdSj36WF+NCOh9XA20DrAHnrHosOSPg='
],
is_merkle_tree: false,
document_hash: '2xXmxEInHGanIixLeS28kOBomIYWuuFKWsFnceNwRQI=',
salt: '105f1cd76154e00f29d36b4966ce9ff4',
salted_hash: 'seUNhKbRhndxYXdH9VA+Ii3IjlnUit0R722ooItjFz8='
};
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.
...
The certificate is valid! The hash in the blockchain and certificate match. This means the document existed at time 2024-08-17T14:45:10.000Z
Verified root hash successfully in 1 blockchains.
The verificationResult
object looks like this with step 13 corresponding to Step.VERIFICATION_FULL_SUCCESS
. Also 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
.
{
step: 13,
type: 0,
currentCert: {
version: 1,
hash_algorithm: 'SHA-256',
blockchains: undefined,
merkle_tree_splice: [
'VB6i9EGb5v8e5+8klTyPofAr60PFJMZGEDW0u1/eH2Y=',
'gJAjot3ZI113UvuanaBBznWRJqpTLpztOWhImxZx0v8=',
'FzmiHQphqpqnbxkA5aykX8gt2Q55Gtp2AAbNUrAAPDw=',
'sj8Sx5llDnx/36qJogZ8YwX1A9LpVEY2DwJYBschke8=',
'6zJvCdGG5iLggdSj36WF+NCOh9XA20DrAHnrHosOSPg='
],
is_merkle_tree: false,
document_hash: '2xXmxEInHGanIixLeS28kOBomIYWuuFKWsFnceNwRQI=',
salt: '105f1cd76154e00f29d36b4966ce9ff4',
salted_hash: 'seUNhKbRhndxYXdH9VA+Ii3IjlnUit0R722ooItjFz8='
},
message: 'Verified root hash successfully in 1 blockchains.',
additionalDataType: 6,
additionalData: 1723905910000,
inputData: {
type: 0,
data: 'This is the data that the certificate will be created for.',
is_merkle_tree: false,
certFileName: 'This is the data tha',
displayName: 'This is the dat[...]'
},
currentTime: 2024-08-17T14:55:28.781Z
}
You can use the value in the step
field of the verificationResult
object like this to determine whether verification was successful
if (verificationResult.step == Step.VERIFICATION_FULL_SUCCESS) {
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.
import { requestCertificateForString, CronStampConfig, LOG_LEVELS } from '@cronstamp/clientlib';
export const customConfig: CronStampConfig = {
LOG_LEVEL: LOG_LEVELS.WARN,
BLOCKCHAIN_XRP_SERVER: 'wss://testnet.xrpl-labs.com',
BLOCKCHAIN_SOLANA_SERVER: 'devnet',
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.