UNPKG

exonum-client

Version:

Light Client for Exonum Blockchain

441 lines (332 loc) 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MapProofError = exports.MapProof = void 0; 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"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } /** * Proof of existence and/or absence of certain elements from a Merkelized * map index. */ var MapProof = /*#__PURE__*/function () { _createClass(MapProof, null, [{ key: "rawKey", /** * Converts a key type to a raw representation, in which keys are not hashed before * Merkle Patricia tree construction. * * @param keyType */ value: function rawKey(keyType) { if (!keyType || typeof keyType.serialize !== 'function') { throw new TypeError('Invalid key type; pass a type with a `serialize` function'); } return { hash: function hash(data) { var bytes = keyType.serialize(data, [], 0); if (bytes.length !== _crypto.HASH_LENGTH) { throw new Error("Invalid raw key; raw keys should have ".concat(_crypto.HASH_LENGTH, "-byte serialization")); } return bytes; } }; } /** * 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) { _classCallCheck(this, MapProof); this.proof = parseProof(json.proof); this.entries = parseEntries(json.entries, keyType, valueType); if (!keyType) { throw new TypeError('No key type provided'); } if (typeof keyType.serialize !== 'function' && typeof keyType.hash !== 'function') { throw new TypeError('No `serialize` or `hash` method in the key type'); } this.keyType = keyType; if (!valueType || typeof valueType.serialize !== 'function') { throw new TypeError('No `serialize` method in the value type'); } this.valueType = valueType; 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(_toConsumableArray(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]; })); } return MapProof; }(); 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; if (typeof keyBytes === 'string') { bytes = (0, _types.hexadecimalToUint8Array)(keyBytes); } else { bytes = new Uint8Array(keyBytes); } return new _ProofPath["default"](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(_toConsumableArray(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) { var buffer = [_constants.MAP_BRANCH_PREFIX]; path.serialize(buffer); _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) { _inherits(MapProofError, _Error); var _super = _createSuper(MapProofError); function MapProofError(type) { var _this2; _classCallCheck(this, MapProofError); switch (type) { case 'malformedProof': _this2 = _super.call(this, 'malformed `proof` part of the proof'); break; case 'malformedEntries': case 'unknownEntryType': case 'ambiguousEntryType': _this2 = _super.call(this, 'malformed `entries` part of the proof'); break; case 'embeddedPaths': _this2 = _super.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 = _super.call(this, "duplicate ".concat(arguments.length <= 1 ? undefined : arguments[1], " in proof")); break; case 'invalidOrdering': _this2 = _super.call(this, 'invalid path ordering'); break; case 'nonTerminalNode': _this2 = _super.call(this, 'non-terminal isolated node in proof'); break; default: _this2 = _super.call(this, type); } return _this2; } return MapProofError; }( /*#__PURE__*/_wrapNativeSuper(Error)); exports.MapProofError = MapProofError;