@blockcerts/hashlink-verifier
Version:
Handling hashlinks in Blockcerts
91 lines (80 loc) • 3.33 kB
text/typescript
import { Hashlink } from './hashlink/Hashlink';
import * as codecs from './hashlink/codecs';
import type { HashlinkModel } from './models/Hashlink';
export class HashlinkVerifier {
private readonly hl: Hashlink;
private hashlinkTable: {[ hashlink: string ]: HashlinkModel } = {}; // keep decoded hashlinks in memory
constructor () {
this.hl = new Hashlink();
this.hl.use(new codecs.MultihashSha2256());
this.hl.use(new codecs.MultihashBlake2b64());
this.hl.use(new codecs.MultibaseBase58btc());
}
/**
* decode method, abstract wrapper over Hashlink class from digital bazaar hashlink package
*
* @param {string} hashlink: the hashlink to be decoded. In this instance it expects a url to be specified.
* @param {function} onHashlinkUrlDecoded: a callback function called when the source url has been discovered to enable
* early manipulation (ie: update image in DOM).
*/
async decode (hashlink: string, onHashlinkUrlDecoded?: (url: string) => void): Promise<HashlinkModel> {
const decodedHashlink: HashlinkModel = await this.hl.decode({ hashlink });
// console.log('hashlink decoded', decodedHashlink);
if (!decodedHashlink.meta && !decodedHashlink.meta.url?.length) {
throw new Error('unparseable document, no url provided as meta data');
}
this.hashlinkTable[hashlink] = decodedHashlink;
const sourceUrl = decodedHashlink.meta.url[0];
onHashlinkUrlDecoded?.(sourceUrl);
return decodedHashlink;
}
async verifyHashlinkTable (): Promise<boolean> {
const hashlinks = Object.keys(this.hashlinkTable);
for (let i = 0; i < hashlinks.length; i++) {
await this.verify(hashlinks[i])
.catch(error => console.log('caught an hashlink exception', error));
}
return true;
}
/**
* verify method, abstract wrapper over Hashlink class from digital bazaar hashlink package
*
* @param {string} hashlink: the hashlink to be decoded. It will lookup in the previously decoded hashlinks table.
* if not found it will decode the hashlink before verification.
*/
async verify (hashlink: string): Promise<boolean> {
const sourceUrl = await this.getSourceUrlFromHashlink(hashlink);
let imageData;
await fetch(sourceUrl)
.then(async response => await response.text())
.then(data => {
imageData = data;
});
const textEncoder = new TextEncoder();
const verified = await this.hl.verify({
data: textEncoder.encode(imageData), // needs an ArrayBuffer
hashlink
});
if (verified) {
console.log(`hashlink ${hashlink} bound to ${sourceUrl} was successfully verified`);
} else {
throw new Error(`Hashlink ${hashlink} does not match data from url ${sourceUrl}`);
}
return verified;
}
hasHashlinksToVerify (): boolean {
return Object.keys(this.hashlinkTable).length > 0;
}
private async getSourceUrlFromHashlink (hashlink: string): Promise<string> {
let decodedHashlink: HashlinkModel;
if (this.hashlinkTable[hashlink]) {
decodedHashlink = this.hashlinkTable[hashlink];
} else {
decodedHashlink = await this.decode(hashlink);
}
return this.getMetaUrl(decodedHashlink);
}
private getMetaUrl (decodedHashlink: HashlinkModel): string {
return decodedHashlink.meta.url[0];
}
}