UNPKG

crypto-conditions

Version:

Implementation of crypto-conditions in JavaScript

495 lines (415 loc) 20.5 kB
"use strict"; var _Reflect$construct = require("@babel/runtime-corejs3/core-js-stable/reflect/construct"); var _sliceInstanceProperty2 = require("@babel/runtime-corejs3/core-js-stable/instance/slice"); var _Array$from2 = require("@babel/runtime-corejs3/core-js-stable/array/from"); var _Symbol = require("@babel/runtime-corejs3/core-js-stable/symbol"); var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method"); var _Array$isArray = require("@babel/runtime-corejs3/core-js-stable/array/is-array"); var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); _Object$defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from")); var _set = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set")); var _sort = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/sort")); var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); var _every = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/every")); var _reduce = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reduce")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass")); var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/getPrototypeOf")); var _condition = _interopRequireDefault(require("../lib/condition")); var _fulfillment = _interopRequireDefault(require("../lib/fulfillment")); var _baseSha = _interopRequireDefault(require("./base-sha256")); var _missingDataError = _interopRequireDefault(require("../errors/missing-data-error")); var _isInteger = _interopRequireDefault(require("../util/is-integer")); var _fingerprint = require("../schemas/fingerprint"); function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof _Symbol !== "undefined" && _getIteratorMethod(o) || o["@@iterator"]; if (!it) { if (_Array$isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { var _context18; if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = _sliceInstanceProperty2(_context18 = Object.prototype.toString.call(o)).call(_context18, 8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return _Array$from2(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 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 _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = _Reflect$construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !_Reflect$construct) return false; if (_Reflect$construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } var CONDITION = 'condition'; var FULFILLMENT = 'fulfillment'; /** * THRESHOLD-SHA-256: Threshold gate condition using SHA-256. * * Threshold conditions can be used to create m-of-n multi-signature groups. * * Threshold conditions can represent the AND operator by setting the threshold * to equal the number of subconditions (n-of-n) or the OR operator by setting * the thresold to one (1-of-n). * * Since threshold conditions operate on conditions, they can be nested as well * which allows the creation of deep threshold trees of public keys. * * By using Merkle trees, threshold fulfillments do not need to to provide the * structure of unfulfilled subtrees. That means only the public keys that are * actually used in a fulfillment, will actually appear in the fulfillment, * saving space. * * One way to formally interpret a threshold condition is as a booleanthreshold * gate. A tree of threshold conditions forms a boolean threshold circuit. * * THRESHOLD-SHA-256 is assigned the type ID 2. It relies on the SHA-256 and * THRESHOLD feature suites which corresponds to a feature bitmask of 0x09. */ var ThresholdSha256 = /*#__PURE__*/function (_BaseSha) { (0, _inherits2.default)(ThresholdSha256, _BaseSha); var _super = _createSuper(ThresholdSha256); function ThresholdSha256() { var _this; (0, _classCallCheck2.default)(this, ThresholdSha256); _this = _super.call(this); _this.threshold = null; _this.subconditions = []; return _this; } /** * Add a subcondition (unfulfilled). * * This can be used to generate a new threshold condition from a set of * subconditions or to provide a non-fulfilled subcondition when creating a * threshold fulfillment. * * @param {Condition|String} subcondition Condition object or URI string * representing a new subcondition to be added. */ (0, _createClass2.default)(ThresholdSha256, [{ key: "addSubcondition", value: function addSubcondition(subcondition) { if (typeof subcondition === 'string') { subcondition = _condition.default.fromUri(subcondition); } else if (!(subcondition instanceof _condition.default)) { throw new Error('Subconditions must be URIs or objects of type Condition'); } this.subconditions.push({ type: CONDITION, body: subcondition }); } /** * Add a fulfilled subcondition. * * When constructing a threshold fulfillment, this method allows you to * provide a fulfillment for one of the subconditions. * * Note that you do **not** have to add the subcondition if you're adding the * fulfillment. The condition can be calculated from the fulfillment and will * be added automatically. * * @param {Fulfillment|String} subfulfillment Fulfillment object or URI string * representing a new subfulfillment to be added. */ }, { key: "addSubfulfillment", value: function addSubfulfillment(subfulfillment) { if (typeof subfulfillment === 'string') { subfulfillment = _fulfillment.default.fromUri(subfulfillment); } else if (!(subfulfillment instanceof _fulfillment.default)) { throw new Error('Subfulfillments must be URIs or objects of type Fulfillment'); } this.subconditions.push({ type: FULFILLMENT, body: subfulfillment }); } /** * Set the threshold. * * Determines the threshold that is used to consider this condition fulfilled. * If the number of valid subfulfillments is greater or equal to this number, * the threshold condition is considered to be fulfilled. * * @param {Number} threshold Integer threshold */ }, { key: "setThreshold", value: function setThreshold(threshold) { if (!(0, _isInteger.default)(threshold) || threshold < 1) { throw new TypeError('Threshold must be a integer greater than zero, was: ' + threshold); } this.threshold = threshold; } /** * Get set of used type names. * * This is a type of condition that can contain subconditions. A complete * set of subtypes must contain all types that must be supported in order to * validate this fulfillment. Therefore, we need to join the type of this * fulfillment with all of the sets of subtypes for each of the subconditions. * * @return {Number} Complete set of types for this fulfillment. */ }, { key: "getSubtypes", value: function getSubtypes() { var _context; var typeSets = (0, _map.default)(_context = this.subconditions).call(_context, function (x) { var _context2; return (0, _concat.default)(_context2 = (0, _from.default)(x.body.getSubtypes())).call(_context2, x.body.getTypeName()); }); var subtypes = new _set.default((0, _concat.default)(Array.prototype).apply([], typeSets)); // Never include our own type as a subtype. The reason is that we already // know that the validating implementation knows how to interpret this type, // otherwise it wouldn't be able to verify this fulfillment to begin with. subtypes.delete(this.constructor.TYPE_NAME); return subtypes; } /** * Comparison function used to pre-sort conditions due to lack of sorting * support in our current DER encoder of choice. * * See: https://github.com/indutny/asn1.js/issues/80 * * @param {Condition} a First condition to compare * @param {Condition} b Second condition to compare * * @private */ }, { key: "getFingerprintContents", value: /** * Produce the contents of the condition hash. * * This function is called internally by the `getCondition` method. * * @return {Buffer} Encoded contents of fingerprint hash. * * @private */ function getFingerprintContents() { var _context3, _context4, _context5; return _fingerprint.ThresholdFingerprintContents.encode({ threshold: this.threshold, subconditions: (0, _map.default)(_context3 = (0, _sort.default)(_context4 = (0, _map.default)(_context5 = this.subconditions).call(_context5, function (x) { return x.body instanceof _condition.default ? x.body : x.body.getCondition(); })).call(_context4, ThresholdSha256.compareConditions)).call(_context3, function (x) { return x.getAsn1Json(); }) }); } /** * Calculate the cost of fulfilling this condition. * * In a threshold condition, the cost consists of the t most expensive * subconditions plus n times 32 where t is the threshold and n is the * total number of subconditions. * * @return {Number} Expected maximum cost to fulfill this condition * @private */ }, { key: "calculateCost", value: function calculateCost() { var _context6; // Calculate length of longest fulfillments var subconditions = (0, _map.default)(_context6 = this.subconditions).call(_context6, this.constructor.getSubconditionCost); var worstCaseFulfillmentsCost = this.constructor.calculateWorstCaseLength(this.threshold, subconditions); if (worstCaseFulfillmentsCost === -Infinity) { throw new _missingDataError.default('Insufficient number of subconditions to meet the threshold'); } return worstCaseFulfillmentsCost + 1024 * subconditions.length; } }, { key: "parseJson", value: function parseJson(json) { this.setThreshold(json.threshold); if (json.subfulfillments) { var _iterator = _createForOfIteratorHelper(json.subfulfillments), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var fulfillmentJson = _step.value; this.addSubfulfillment(_fulfillment.default.fromJson(fulfillmentJson)); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } if (json.subconditions) { var _iterator2 = _createForOfIteratorHelper(json.subconditions), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var conditionJson = _step2.value; this.addSubcondition(_condition.default.fromJson(conditionJson)); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } } } }, { key: "parseAsn1JsonPayload", value: function parseAsn1JsonPayload(json) { this.setThreshold(json.subfulfillments.length); if (json.subfulfillments) { var _iterator3 = _createForOfIteratorHelper(json.subfulfillments), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var fulfillmentJson = _step3.value; this.addSubfulfillment(_fulfillment.default.fromAsn1Json(fulfillmentJson)); } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } } if (json.subconditions) { var _iterator4 = _createForOfIteratorHelper(json.subconditions), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var conditionJson = _step4.value; this.addSubcondition(_condition.default.fromAsn1Json(conditionJson)); } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } } }, { key: "getAsn1JsonPayload", value: function getAsn1JsonPayload() { var _context7, _context8, _context9, _context10, _context11, _context12, _context13, _context14; var fulfillments = (0, _sort.default)(_context7 = (0, _filter.default)(_context8 = this.subconditions).call(_context8, function (x) { return x.type === FULFILLMENT; })).call(_context7, function (a, b) { return a.body.getCondition().getCost() - b.body.getCondition().getCost(); }); var conditions = (0, _filter.default)(_context9 = this.subconditions).call(_context9, function (x) { return x.type === CONDITION; }); if (fulfillments.length < this.threshold) { throw new Error('Not enough fulfillments'); } var minimalFulfillments = (0, _slice.default)(fulfillments).call(fulfillments, 0, this.threshold); var remainingConditions = (0, _concat.default)(_context10 = (0, _map.default)(conditions).call(conditions, function (x) { return x.body; })).call(_context10, (0, _map.default)(_context11 = (0, _slice.default)(fulfillments).call(fulfillments, this.threshold)).call(_context11, function (x) { return x.body.getCondition(); })); return { subfulfillments: (0, _map.default)(_context12 = (0, _sort.default)(_context13 = (0, _map.default)(minimalFulfillments).call(minimalFulfillments, function (x) { return x.body; })).call(_context13, ThresholdSha256.compareConditions)).call(_context12, function (x) { return x.getAsn1Json(); }), subconditions: (0, _map.default)(_context14 = (0, _sort.default)(remainingConditions).call(remainingConditions, ThresholdSha256.compareConditions)).call(_context14, function (x) { return x.getAsn1Json(); }) }; } /** * Select the smallest valid set of fulfillments. * * From a set of fulfillments, selects the smallest combination of * fulfillments which meets the given threshold. * * @param {Number} threshold (Remaining) threshold that must be met. * @param {Object[]} fulfillments Set of fulfillments * @return {Object[]} Minimal set of fulfillments. * * @private */ }, { key: "validate", value: /** * Check whether this fulfillment meets all validation criteria. * * This will validate the subfulfillments and verify that there are enough * subfulfillments to meet the threshold. * * @param {Buffer} message Message to validate against. * @return {Boolean} Whether this fulfillment is valid. */ function validate(message) { var _context15; var fulfillments = (0, _filter.default)(_context15 = this.subconditions).call(_context15, function (cond) { return cond.type === FULFILLMENT; }); // Number of fulfilled conditions must meet the threshold if (fulfillments.length < this.threshold) { throw new Error('Threshold not met'); } // But the set must be minimal, there mustn't be any fulfillments // we could take out if (fulfillments.length > this.threshold) { throw new Error('Fulfillment is not minimal'); } // Ensure all subfulfillments are valid return (0, _every.default)(fulfillments).call(fulfillments, function (f) { return f.body.validate(message); }); } }], [{ key: "compareConditions", value: function compareConditions(a, b) { return Buffer.compare(a.serializeBinary(), b.serializeBinary()); } }, { key: "getSubconditionCost", value: function getSubconditionCost(cond) { return cond.type === FULFILLMENT ? cond.body.getCondition().getCost() : cond.body.getCost(); } /** * Calculate the worst case cost of a set of subconditions. * * Given a set of costs C and a threshold t, it returns the sum of the largest * t elements in C. * * @param {Number} threshold Threshold that the remaining subconditions have * to meet. * @param {Number[]} subconditionCosts Set of subconditions. * @return {Number} Maximum cost of a valid, minimal set of fulfillments or * -Infinity if there is no valid set. * * @private */ }, { key: "calculateWorstCaseLength", value: function calculateWorstCaseLength(threshold, subconditionCosts) { var _context16, _context17; if (subconditionCosts.length < threshold) { return -Infinity; } return (0, _reduce.default)(_context16 = (0, _slice.default)(_context17 = (0, _sort.default)(subconditionCosts).call(subconditionCosts, function (a, b) { return a - b; })).call(_context17, -threshold)).call(_context16, function (total, size) { return total + size; }, 0); } }, { key: "calculateSmallestValidFulfillmentSet", value: function calculateSmallestValidFulfillmentSet(threshold, fulfillments) { (0, _sort.default)(fulfillments).call(fulfillments, function (a, b) { return b.size - a.size; }); return (0, _slice.default)(fulfillments).call(fulfillments, 0, threshold); } }]); return ThresholdSha256; }(_baseSha.default); ThresholdSha256.TYPE_ID = 2; ThresholdSha256.TYPE_NAME = 'threshold-sha-256'; ThresholdSha256.TYPE_ASN1_CONDITION = 'thresholdSha256Condition'; ThresholdSha256.TYPE_ASN1_FULFILLMENT = 'thresholdSha256Fulfillment'; ThresholdSha256.TYPE_CATEGORY = 'compound'; // DEPRECATED ThresholdSha256.prototype.addSubconditionUri = ThresholdSha256.prototype.addSubcondition; ThresholdSha256.prototype.addSubfulfillmentUri = ThresholdSha256.prototype.addSubfulfillment; var _default = ThresholdSha256; exports.default = _default;