@cronstamp/clientlib
Version:
Client library for cronstamp, a blockchain-based document timestamping and verification service.
182 lines • 9.61 kB
JavaScript
import { CertificateHelper, getCertificateFromFile, getCertificateFromString } from '../certificate.js';
import { AdditionalDataType, CertificateProcess, InputDataType, MessageType, Step } from './manager.js';
import { hashSiblingHashes, isValidHash, VersionToHashAlgorithm } from '../util/hash.js';
import { blockchainNames } from '../blockchains/blockchains.js';
import { Xrp } from '../blockchains/xrp.js';
import { Solana } from '../blockchains/solana.js';
/**
* Start certificate verification for a string
* @param certificate either as type Certificate, Json string or File
* @param message
*/
export function verifyCertificateForString(certificate, message) {
return new VerificationProcess(certificate, {
type: InputDataType.STRING,
data: message
});
}
/**
* Start certificate verification for a file
* @param certificate either as type Certificate, Json string or File
* @param file
*/
export function verifyCertificateForFile(certificate, file) {
return new VerificationProcess(certificate, {
type: InputDataType.FILE,
data: file
});
}
/**
* Start certificate verification for an existing hash
* @param certificate
* @param hash base64 encoded hash
*/
export function verifyCertificateForHash(certificate, hash) {
return new VerificationProcess(certificate, {
type: InputDataType.HASH,
data: hash
});
}
/**
* Start verify process
* @param certificate
* @param type
* @param data
*/
export function verifyCertificate(certificate, type, data) {
return new VerificationProcess(certificate, {
type: type,
data: data
});
}
export class VerificationProcess extends CertificateProcess {
// the certificate to verify, is not modified
certificateToVerify;
certificateToParse;
skipBlockchains;
constructor(certificate, data) {
super(data, 'Verify');
if (certificate instanceof File || typeof certificate === 'string') {
this.certificateToParse = certificate;
}
else {
this.certificateToVerify = certificate;
}
this.skipBlockchains = [];
}
async start() {
// the general idea is to incrementally build up the internal cert of CertificateProcess and compare it to the user provided certificate and blockchain
// after super.start() a certificate with document_hash is available
let preparation = await super.start();
if (preparation.step != Step.HASH_INPUT_DATA || preparation.type != MessageType.SUCCESS) {
return preparation;
}
try {
// hash data again and compare resulting hash to user provided certificate
this.step = Step.VERIFICATION_CHECK_CERTIFICATE_SYNTAX;
if (this.certificateToParse !== undefined) {
if (this.certificateToParse instanceof File) {
this.certificateToVerify = await getCertificateFromFile(this.certificateToParse);
}
else {
this.certificateToVerify = await getCertificateFromString(this.certificateToParse);
}
}
if (!new CertificateHelper(this.certificateToVerify).hasData()) {
return this.sendError('Missing or invalid data in certificate!', AdditionalDataType.INVALID_SYNTAX_CERTIFICATE, this.certificateToVerify);
}
let unknownBlockchains = this.certificateToVerify.meta.blockchains.filter((blockchain) => !blockchainNames.includes(blockchain));
if (unknownBlockchains.length > 0) {
return this.sendError('Unknown blockchain name found in certificate: ' + unknownBlockchains.join(', '));
}
this.sendMessage('Fully loaded certificate from file or directly.', MessageType.SUCCESS, AdditionalDataType.PARSED_CERTIFICATE, this.certificateToVerify);
// hash data again and compare resulting hash to user provided certificate
this.step = Step.VERIFICATION_COMPARE_SALTED_HASHES;
if (this.certHelper.cert.document_hash != this.certificateToVerify.document_hash) {
return this.sendError('Locally calculated data hash does not match hash in provided certificate: ' +
this.certHelper.cert.document_hash +
' != ' +
this.certificateToVerify.document_hash);
}
this.certHelper.cert.meta.salt = this.certificateToVerify.meta.salt;
this.certHelper.cert.meta.blockchains = this.certificateToVerify.meta.blockchains;
let saltedHash = await this.certHelper.calculateSaltedHash();
this.sendMessage('Locally calculated data hash matches hash in provided certificate.', MessageType.SUCCESS);
// blockchains are only added to the cert, once they are verified
this.certHelper.cert.blockchains = {};
// verify in all blockchains in the certificate
for (let blockchain of this.certificateToVerify.meta.blockchains) {
if (this.skipBlockchains.includes(blockchain)) {
continue;
}
try {
const result = await this.verifySingleBlockchain(blockchain, saltedHash);
if (result.type == MessageType.SUCCESS && result.step === Step.VERIFICATION_VERIFY_SINGLE_BLOCKCHAIN) {
this.certHelper.cert.blockchains[blockchain] = this.certificateToVerify.blockchains[blockchain];
}
}
catch (error) {
this.handleException(error);
}
finally {
}
}
// done handling blockchain specific verification
this.currentBlockchain = undefined;
// get timestamp from certificate ( based on all blockchains )
let timestamp = new CertificateHelper(this.certificateToVerify).getCertificateUnixTime();
//require at least one verified blockchain
const requiredBlockchains = this.certHelper.cert.meta.blockchains.filter((b) => !this.skipBlockchains.includes(b));
const blockchainsVerified = Object.keys(this.certHelper.cert.blockchains);
if (blockchainsVerified.length < requiredBlockchains.length) {
if (blockchainsVerified.length == 0) {
return this.sendError(`The blockchains [${requiredBlockchains.join(', ')}] included in this certificate were not verified!`);
}
if (this.skipBlockchains.length > 0) {
return this.sendError(`The blockchains [${blockchainsVerified.join(', ')}] of the blockchains [${requiredBlockchains.join(', ')}] included in this certificate were successfully verified!`);
}
else {
return this.sendError(`Only the blockchains [${blockchainsVerified.join(', ')}] of the required blockchains [${requiredBlockchains.join(', ')}] included in this certificate were successfully verified!`);
}
}
// this step only has type success and always means verification was fully successful
this.step = Step.VERIFICATION_ALL_BLOCKCHAINS_SUCCEEDED;
return this.sendMessage('Verified root hash successfully in ' + blockchainsVerified.length + ' blockchains.', MessageType.SUCCESS, AdditionalDataType.TIMESTAMP, timestamp);
}
catch (e) {
return this.handleException(e);
}
}
async verifySingleBlockchain(blockchain, saltedHash) {
// start verification for one specific blockchain
this.currentBlockchain = blockchain;
this.step = Step.VERIFICATION_VERIFY_SINGLE_BLOCKCHAIN;
let blockchainData = this.certificateToVerify.blockchains[blockchain];
if (!(blockchain in this.certificateToVerify.blockchains &&
this.certificateToVerify?.blockchains[blockchain]?.transaction !== undefined &&
this.certificateToVerify.blockchains[blockchain]?.block_timestamp !== undefined &&
this.certificateToVerify.blockchains[blockchain]?.merkle_tree_splice !== undefined)) {
return this.sendError(`Missing data for blockchain ${blockchain} expected keys "transaction", "block_timestamp" and "merkle_tree_splice".`);
}
// calculate root node hash
if (!blockchainData.merkle_tree_splice.every((hash) => hash === '' || isValidHash(hash, VersionToHashAlgorithm[this.certificateToVerify.meta.version]))) {
return this.sendError('Invalid hash in merkletree splice for blockchain: ' + blockchain);
}
let rootHash = saltedHash;
for (let siblingHash of blockchainData.merkle_tree_splice) {
rootHash = await hashSiblingHashes(rootHash, siblingHash);
}
this.sendMessage('Calculated merkletree root hash: ' + rootHash, MessageType.PROGRESS, AdditionalDataType.HASH, rootHash);
switch (blockchain) {
case 'xrp': {
return await new Xrp(this.log).verifyRootHash(rootHash, blockchainData.block_timestamp, blockchainData.transaction, this);
}
case 'solana': {
return await new Solana(this.log).verifyRootHash(rootHash, blockchainData.block_timestamp, blockchainData.transaction, this);
}
default:
return this.sendError('Unknown blockchain provided in certificate: ' + blockchain);
}
}
}
//# sourceMappingURL=verify.js.map