UNPKG

@iden3/js-jsonld-merklization

Version:
130 lines (129 loc) 4.86 kB
import { compact } from 'jsonld'; import { Merkletree } from '@iden3/js-merkletree'; import { RDFDataset } from './rdf-dataset'; import { DEFAULT_HASHER } from './poseidon'; import { addEntriesToMerkleTree, getMerkleTreeInitParam } from './merkle-tree'; import { RDFEntry } from './rdf-entry'; import { Path } from './path'; import { MtValue } from './mt-value'; import { convertAnyToString, convertStringToXsdValue } from './utils'; import { getDocumentLoader, getHasher } from './options'; export class Merklizer { constructor(srcDoc = null, mt = null, hasher = DEFAULT_HASHER, entries = new Map(), compacted = null, documentLoader = getDocumentLoader()) { this.srcDoc = srcDoc; this.mt = mt; this.hasher = hasher; this.entries = entries; this.compacted = compacted; this.documentLoader = documentLoader; if (!mt) { const { db, writable, maxLevels } = getMerkleTreeInitParam(); this.mt = new Merkletree(db, writable, maxLevels); } } async proof(p) { const kHash = await p.mtEntry(); const { proof } = await this.mt.generateProof(kHash); if (proof.existence) { if (!this.entries.has(kHash.toString())) { throw new Error('error: [assertion] no entry found while existence is true'); } const entry = this.entries.get(kHash.toString()); const value = new MtValue(entry.value, this.hasher); return { proof, value }; } return { proof }; } mkValue(val) { return new MtValue(val, this.hasher); } async resolveDocPath(path, opts) { const realPath = await Path.fromDocument(null, this.srcDoc, path, opts); realPath.hasher = this.hasher; return realPath; } async entry(path) { const key = await path.mtEntry(); const e = this.entries.get(key.toString()); if (!e) { throw new Error('entry not found'); } return e; } // JSONLDType returns the JSON-LD type of the given path. If there is no literal // by this path, it returns an error. async jsonLDType(path) { const entry = await this.entry(path); return entry.dataType; } async root() { return this.mt.root(); } rawValue(path) { let parts = path.parts; let obj = this.compacted; const traversedParts = []; const currentPath = () => traversedParts.join(' / '); while (parts.length > 0) { const p = parts[0]; if (typeof p === 'string') { traversedParts.push(p); obj = obj[p] ?? obj['@graph'][p]; if (!obj) { throw new Error('value not found'); } } else if (typeof p === 'number') { traversedParts.push(p.toString()); obj = this.rvExtractArrayIdx(obj, p); } else { throw new Error(`unexpected type of path ${currentPath()}`); } parts = parts.slice(1); } if (typeof obj['@value'] !== 'undefined') { return obj['@value']; } return obj; } rvExtractArrayIdx(obj, idx) { const isArray = Array.isArray(obj); if (!isArray) { throw new Error('expected array'); } if (idx < 0 || idx >= obj.length) { throw new Error('index is out of range'); } return obj[idx]; } static async merklizeJSONLD(docStr, opts) { const hasher = getHasher(opts); const documentLoader = getDocumentLoader(opts); const mz = new Merklizer(docStr, null, hasher, new Map(), null, documentLoader); const doc = JSON.parse(mz.srcDoc); const dataset = await RDFDataset.fromDocument(doc, documentLoader); const entries = await RDFEntry.fromDataSet(dataset, hasher); for (const e of entries) { const k = await e.getKeyMtEntry(); mz.entries.set(k.toString(), e); } await addEntriesToMerkleTree(mz.mt, entries); mz.compacted = await compact(doc, {}, { documentLoader, base: null, compactArrays: true, compactToRelative: true }); return mz; } static async hashValue(dataType, value) { return this.hashValueWithHasher(DEFAULT_HASHER, dataType, value); } static async hashValueWithHasher(h, dataType, value) { const valueStr = convertAnyToString(value, dataType); const xsdValue = convertStringToXsdValue(dataType, valueStr, h.prime()); return await MtValue.mkValueMtEntry(h, xsdValue); } get options() { return { hasher: this.hasher, documentLoader: this.documentLoader }; } }