UNPKG

exonum-client-cis

Version:

Light Client for Exonum CIS Blockchain

375 lines (291 loc) 11.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.MapProofError = exports.MapProof = void 0; var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _binarySearch = _interopRequireDefault(require("binary-search")); var _crypto = require("../crypto"); var _hexadecimal = require("../types/hexadecimal"); var _constants = require("./constants"); var _ProofPath = _interopRequireDefault(require("./ProofPath")); var _types = require("../types"); /** * Proof of existence and/or absence of certain elements from a Merkelized * map index. */ var MapProof = /** * Creates a new instance of a proof. * * @param {Object} json * JSON object containing (untrusted) proof * @param {{serialize: (any) => Array<number>}} keyType * Type of keys used in the underlying Merkelized map. Usually, `PublicKey` * or `Hash`. The keys must be serializable. * @param {{ serialize: (any) => Array<number>}} valueType * Type of values used in the underlying Merkelized map. Usually, it should * be a type created with the `newType` function. The type must be serializable. * @throws {MapProofError} * if the proof is malformed */ function MapProof(json, keyType, valueType) { (0, _classCallCheck2["default"])(this, MapProof); if (!valueType) { throw new TypeError('Invalid value type'); } if (typeof valueType.encode === 'function' && typeof valueType.serialize !== 'function') { valueType.serialize = function (data, _buff, _offset) { return this.encode(data).finish(); }; } else if (typeof valueType.serialize !== 'function') { throw new TypeError('No `hash` or `serialize` method in the value type'); } this.valueType = valueType; this.proof = parseProof(json.proof); this.entries = parseEntries(json.entries, keyType, valueType); if (!keyType || typeof keyType.serialize !== 'function') { throw new TypeError('No `serialize` method in the key type'); } this.keyType = keyType; precheckProof.call(this); var completeProof = this.proof.concat(this.entries).sort(function (_ref, _ref2) { var pathA = _ref.path; var pathB = _ref2.path; return pathA.compare(pathB); }); // This check is required as duplicate paths can be introduced by entries // (further, it's generally possible that two different entry keys lead // to the same `ProofPath`). for (var i = 1; i < completeProof.length; i++) { var _ref3 = [completeProof[i - 1], completeProof[i]], pathA = _ref3[0].path, pathB = _ref3[1].path; if (pathA.compare(pathB) === 0) { throw new MapProofError('duplicatePath', pathA); } } var rootHash = (0, _types.hexadecimalToUint8Array)(collect(completeProof.filter(function (_ref4) { var hash = _ref4.hash; return !!hash; }))); this.merkleRoot = (0, _crypto.hash)([_constants.MAP_PREFIX].concat((0, _toConsumableArray2["default"])(rootHash))); this.missingKeys = new Set(this.entries.filter(function (e) { return e.missing !== undefined; }).map(function (_ref5) { var missing = _ref5.missing; return missing; })); this.entries = new Map(this.entries.filter(function (e) { return e.key !== undefined; }).map(function (_ref6) { var key = _ref6.key, value = _ref6.value; return [key, value]; })); }; exports.MapProof = MapProof; function parseProof(proof) { if (!Array.isArray(proof)) { throw new MapProofError('malformedProof'); } var validEntries = proof.every(function (_ref7) { var path = _ref7.path, hash = _ref7.hash; return /^[01]{1,256}$/.test(path) && /^[0-9a-f]{64}$/i.test(hash); }); if (!validEntries) { throw new MapProofError('malformedProof'); } return proof.map(function (_ref8) { var path = _ref8.path, hash = _ref8.hash; return { path: new _ProofPath["default"](path), hash: hash }; }); } function parseEntries(entries, keyType, valueType) { function createPath(data) { var keyBytes = typeof keyType.hash === 'function' ? keyType.hash(data) : (0, _crypto.hash)(keyType.serialize(data, [], 0)); var bytes = (0, _types.hexadecimalToUint8Array)(keyBytes); return new _ProofPath["default"](new Uint8Array(bytes)); } if (!Array.isArray(entries)) { throw new MapProofError('malformedEntries'); } return entries.map(function (_ref9) { var missing = _ref9.missing, key = _ref9.key, value = _ref9.value; if (missing === undefined && (key === undefined || value === undefined)) { throw new MapProofError('unknownEntryType'); } if (missing !== undefined && (key !== undefined || value !== undefined)) { throw new MapProofError('ambiguousEntryType'); } if (missing !== undefined) { return { missing: missing, path: createPath(missing) }; } return { key: key, value: value, path: createPath(key), hash: (0, _crypto.hash)([_constants.BLOB_PREFIX].concat((0, _toConsumableArray2["default"])(valueType.serialize(value, [], 0)))) }; }); } /** * @this {MapProof} */ function precheckProof() { var _this = this; // Check that entries in proof are in increasing order for (var i = 1; i < this.proof.length; i++) { var _ref10 = [this.proof[i - 1], this.proof[i]], prevPath = _ref10[0].path, path = _ref10[1].path; switch (prevPath.compare(path)) { case -1: if (path.startsWith(prevPath)) { throw new MapProofError('embeddedPaths', prevPath, path); } break; case 0: throw new MapProofError('duplicatePath', path); case 1: throw new MapProofError('invalidOrdering', prevPath, path); } } // Check that no entry has a prefix among the paths in the proof entries. // In order to do this, it suffices to locate the closest smaller path // in the proof entries and check only it. this.entries.forEach(function (_ref11) { var keyPath = _ref11.path; var index = (0, _binarySearch["default"])(_this.proof, keyPath, function (_ref12, needle) { var path = _ref12.path; return path.compare(needle); }); if (index >= 0) { throw new MapProofError('duplicatePath', keyPath); } var insertionIndex = -index - 1; if (insertionIndex > 0) { var _prevPath = _this.proof[insertionIndex - 1].path; if (keyPath.startsWith(_prevPath)) { throw new MapProofError('embeddedPaths', _prevPath, keyPath); } } }); } function serializeBranchNode(leftHash, rightHash, leftPath, rightPath) { var buffer = [_constants.MAP_BRANCH_PREFIX]; _hexadecimal.Hash.serialize(leftHash, buffer, buffer.length); _hexadecimal.Hash.serialize(rightHash, buffer, buffer.length); leftPath.serialize(buffer); rightPath.serialize(buffer); return buffer; } function serializeIsolatedNode(path, hash) { // `1, ...path.key, 0` is the old `ProofPath` serialization for terminal paths. var buffer = [_constants.MAP_BRANCH_PREFIX, 1].concat((0, _toConsumableArray2["default"])(path.key), [0]); _hexadecimal.Hash.serialize(hash, buffer, buffer.length); return buffer; } function collect(entries) { function hashIsolatedNode(_ref13) { var path = _ref13.path, valueHash = _ref13.hash; var buffer = serializeIsolatedNode(path, valueHash); return (0, _crypto.hash)(buffer); } function hashBranch(left, right) { var buffer = serializeBranchNode(left.hash, right.hash, left.path, right.path); return (0, _crypto.hash)(buffer); } function fold(contour, lastPrefix) { var lastEntry = contour.pop(); var penultimateEntry = contour.pop(); contour.push({ path: lastPrefix, hash: hashBranch(penultimateEntry, lastEntry) }); return contour.length > 1 ? lastPrefix.commonPrefix(contour[contour.length - 2].path) : null; } switch (entries.length) { case 0: return '0000000000000000000000000000000000000000000000000000000000000000'; case 1: if (!entries[0].path.isTerminal()) { throw new MapProofError('nonTerminalNode', entries[0].path); } return hashIsolatedNode(entries[0]); default: var contour = []; // invariant: equal to the common prefix of the 2 last nodes in the contour var lastPrefix = entries[0].path.commonPrefix(entries[1].path); contour.push(entries[0], entries[1]); for (var i = 2; i < entries.length; i++) { var entry = entries[i]; var newPrefix = entry.path.commonPrefix(contour[contour.length - 1].path); while (contour.length > 1 && newPrefix.bitLength < lastPrefix.bitLength) { var foldedPrefix = fold(contour, lastPrefix); if (foldedPrefix) { lastPrefix = foldedPrefix; } } contour.push(entry); lastPrefix = newPrefix; } while (contour.length > 1) { lastPrefix = fold(contour, lastPrefix); } return contour[0].hash; } } /** * Error indicating a malformed `MapProof`. */ var MapProofError = /*#__PURE__*/ function (_Error) { (0, _inherits2["default"])(MapProofError, _Error); function MapProofError(type) { var _this2; (0, _classCallCheck2["default"])(this, MapProofError); switch (type) { case 'malformedProof': _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, 'malformed `proof` part of the proof')); break; case 'malformedEntries': case 'unknownEntryType': case 'ambiguousEntryType': _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, 'malformed `entries` part of the proof')); break; case 'embeddedPaths': _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, "embedded paths in proof: ".concat(arguments.length <= 1 ? undefined : arguments[1], " is a prefix of ").concat(arguments.length <= 2 ? undefined : arguments[2]))); break; case 'duplicatePath': _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, "duplicate ".concat(arguments.length <= 1 ? undefined : arguments[1], " in proof"))); break; case 'invalidOrdering': _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, 'invalid path ordering')); break; case 'nonTerminalNode': _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, 'non-terminal isolated node in proof')); break; default: _this2 = (0, _possibleConstructorReturn2["default"])(this, (0, _getPrototypeOf2["default"])(MapProofError).call(this, type)); } return _this2; } return MapProofError; }((0, _wrapNativeSuper2["default"])(Error)); exports.MapProofError = MapProofError;