crypto-conditions
Version:
Implementation of crypto-conditions in JavaScript
495 lines (415 loc) • 20.5 kB
JavaScript
;
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;