UNPKG

@stellar/stellar-sdk

Version:

A library for working with the Stellar network, including communication with the Horizon and Soroban RPC servers.

332 lines (331 loc) 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildChallengeTx = buildChallengeTx; exports.gatherTxSigners = gatherTxSigners; exports.readChallengeTx = readChallengeTx; exports.verifyChallengeTxSigners = verifyChallengeTxSigners; exports.verifyChallengeTxThreshold = verifyChallengeTxThreshold; exports.verifyTxSignedBy = verifyTxSignedBy; var _randombytes = _interopRequireDefault(require("randombytes")); var _stellarBase = require("@stellar/stellar-base"); var _utils = require("../utils"); var _errors = require("./errors"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _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 _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _toArray(r) { return _arrayWithHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } function buildChallengeTx(serverKeypair, clientAccountID, homeDomain) { var timeout = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 300; var networkPassphrase = arguments.length > 4 ? arguments[4] : undefined; var webAuthDomain = arguments.length > 5 ? arguments[5] : undefined; var memo = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null; var clientDomain = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null; var clientSigningKey = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : null; if (clientAccountID.startsWith("M") && memo) { throw Error("memo cannot be used if clientAccountID is a muxed account"); } var account = new _stellarBase.Account(serverKeypair.publicKey(), "-1"); var now = Math.floor(Date.now() / 1000); var value = (0, _randombytes.default)(48).toString("base64"); var builder = new _stellarBase.TransactionBuilder(account, { fee: _stellarBase.BASE_FEE, networkPassphrase: networkPassphrase, timebounds: { minTime: now, maxTime: now + timeout } }).addOperation(_stellarBase.Operation.manageData({ name: "".concat(homeDomain, " auth"), value: value, source: clientAccountID })).addOperation(_stellarBase.Operation.manageData({ name: "web_auth_domain", value: webAuthDomain, source: account.accountId() })); if (clientDomain) { if (!clientSigningKey) { throw Error("clientSigningKey is required if clientDomain is provided"); } builder.addOperation(_stellarBase.Operation.manageData({ name: "client_domain", value: clientDomain, source: clientSigningKey })); } if (memo) { builder.addMemo(_stellarBase.Memo.id(memo)); } var transaction = builder.build(); transaction.sign(serverKeypair); return transaction.toEnvelope().toXDR("base64").toString(); } function readChallengeTx(challengeTx, serverAccountID, networkPassphrase, homeDomains, webAuthDomain) { var _transaction$timeBoun; if (serverAccountID.startsWith("M")) { throw Error("Invalid serverAccountID: multiplexed accounts are not supported."); } var transaction; try { transaction = new _stellarBase.Transaction(challengeTx, networkPassphrase); } catch (_unused) { try { transaction = new _stellarBase.FeeBumpTransaction(challengeTx, networkPassphrase); } catch (_unused2) { throw new _errors.InvalidChallengeError("Invalid challenge: unable to deserialize challengeTx transaction string"); } throw new _errors.InvalidChallengeError("Invalid challenge: expected a Transaction but received a FeeBumpTransaction"); } var sequence = Number.parseInt(transaction.sequence, 10); if (sequence !== 0) { throw new _errors.InvalidChallengeError("The transaction sequence number should be zero"); } if (transaction.source !== serverAccountID) { throw new _errors.InvalidChallengeError("The transaction source account is not equal to the server's account"); } if (transaction.operations.length < 1) { throw new _errors.InvalidChallengeError("The transaction should contain at least one operation"); } var _transaction$operatio = _toArray(transaction.operations), operation = _transaction$operatio[0], subsequentOperations = _transaction$operatio.slice(1); if (!operation.source) { throw new _errors.InvalidChallengeError("The transaction's operation should contain a source account"); } var clientAccountID = operation.source; var memo = null; if (transaction.memo.type !== _stellarBase.MemoNone) { if (clientAccountID.startsWith("M")) { throw new _errors.InvalidChallengeError("The transaction has a memo but the client account ID is a muxed account"); } if (transaction.memo.type !== _stellarBase.MemoID) { throw new _errors.InvalidChallengeError("The transaction's memo must be of type `id`"); } memo = transaction.memo.value; } if (operation.type !== "manageData") { throw new _errors.InvalidChallengeError("The transaction's operation type should be 'manageData'"); } if (transaction.timeBounds && Number.parseInt((_transaction$timeBoun = transaction.timeBounds) === null || _transaction$timeBoun === void 0 ? void 0 : _transaction$timeBoun.maxTime, 10) === _stellarBase.TimeoutInfinite) { throw new _errors.InvalidChallengeError("The transaction requires non-infinite timebounds"); } if (!_utils.Utils.validateTimebounds(transaction, 60 * 5)) { throw new _errors.InvalidChallengeError("The transaction has expired"); } if (operation.value === undefined) { throw new _errors.InvalidChallengeError("The transaction's operation values should not be null"); } if (!operation.value) { throw new _errors.InvalidChallengeError("The transaction's operation value should not be null"); } if (Buffer.from(operation.value.toString(), "base64").length !== 48) { throw new _errors.InvalidChallengeError("The transaction's operation value should be a 64 bytes base64 random string"); } if (!homeDomains) { throw new _errors.InvalidChallengeError("Invalid homeDomains: a home domain must be provided for verification"); } var matchedHomeDomain; if (typeof homeDomains === "string") { if ("".concat(homeDomains, " auth") === operation.name) { matchedHomeDomain = homeDomains; } } else if (Array.isArray(homeDomains)) { matchedHomeDomain = homeDomains.find(function (domain) { return "".concat(domain, " auth") === operation.name; }); } else { throw new _errors.InvalidChallengeError("Invalid homeDomains: homeDomains type is ".concat(_typeof(homeDomains), " but should be a string or an array")); } if (!matchedHomeDomain) { throw new _errors.InvalidChallengeError("Invalid homeDomains: the transaction's operation key name does not match the expected home domain"); } var _iterator = _createForOfIteratorHelper(subsequentOperations), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var op = _step.value; if (op.type !== "manageData") { throw new _errors.InvalidChallengeError("The transaction has operations that are not of type 'manageData'"); } if (op.source !== serverAccountID && op.name !== "client_domain") { throw new _errors.InvalidChallengeError("The transaction has operations that are unrecognized"); } if (op.name === "web_auth_domain") { if (op.value === undefined) { throw new _errors.InvalidChallengeError("'web_auth_domain' operation value should not be null"); } if (op.value.compare(Buffer.from(webAuthDomain))) { throw new _errors.InvalidChallengeError("'web_auth_domain' operation value does not match ".concat(webAuthDomain)); } } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } if (!verifyTxSignedBy(transaction, serverAccountID)) { throw new _errors.InvalidChallengeError("Transaction not signed by server: '".concat(serverAccountID, "'")); } return { tx: transaction, clientAccountID: clientAccountID, matchedHomeDomain: matchedHomeDomain, memo: memo }; } function verifyChallengeTxThreshold(challengeTx, serverAccountID, networkPassphrase, threshold, signerSummary, homeDomains, webAuthDomain) { var signers = signerSummary.map(function (signer) { return signer.key; }); var signersFound = verifyChallengeTxSigners(challengeTx, serverAccountID, networkPassphrase, signers, homeDomains, webAuthDomain); var weight = 0; var _loop = function _loop() { var _signerSummary$find; var signer = _signersFound[_i]; var sigWeight = ((_signerSummary$find = signerSummary.find(function (s) { return s.key === signer; })) === null || _signerSummary$find === void 0 ? void 0 : _signerSummary$find.weight) || 0; weight += sigWeight; }; for (var _i = 0, _signersFound = signersFound; _i < _signersFound.length; _i++) { _loop(); } if (weight < threshold) { throw new _errors.InvalidChallengeError("signers with weight ".concat(weight, " do not meet threshold ").concat(threshold, "\"")); } return signersFound; } function verifyChallengeTxSigners(challengeTx, serverAccountID, networkPassphrase, signers, homeDomains, webAuthDomain) { var _readChallengeTx = readChallengeTx(challengeTx, serverAccountID, networkPassphrase, homeDomains, webAuthDomain), tx = _readChallengeTx.tx; var serverKP; try { serverKP = _stellarBase.Keypair.fromPublicKey(serverAccountID); } catch (err) { throw new Error("Couldn't infer keypair from the provided 'serverAccountID': ".concat(err.message)); } var clientSigners = new Set(); var _iterator2 = _createForOfIteratorHelper(signers), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _signer = _step2.value; if (_signer === serverKP.publicKey()) { continue; } if (_signer.charAt(0) !== "G") { continue; } clientSigners.add(_signer); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (clientSigners.size === 0) { throw new _errors.InvalidChallengeError("No verifiable client signers provided, at least one G... address must be provided"); } var clientSigningKey; var _iterator3 = _createForOfIteratorHelper(tx.operations), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var op = _step3.value; if (op.type === "manageData" && op.name === "client_domain") { if (clientSigningKey) { throw new _errors.InvalidChallengeError("Found more than one client_domain operation"); } clientSigningKey = op.source; } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } var allSigners = [serverKP.publicKey()].concat(_toConsumableArray(Array.from(clientSigners))); if (clientSigningKey) { allSigners.push(clientSigningKey); } var signersFound = gatherTxSigners(tx, allSigners); var serverSignatureFound = false; var clientSigningKeySignatureFound = false; for (var _i2 = 0, _signersFound2 = signersFound; _i2 < _signersFound2.length; _i2++) { var signer = _signersFound2[_i2]; if (signer === serverKP.publicKey()) { serverSignatureFound = true; } if (signer === clientSigningKey) { clientSigningKeySignatureFound = true; } } if (!serverSignatureFound) { throw new _errors.InvalidChallengeError("Transaction not signed by server: '".concat(serverKP.publicKey(), "'")); } if (clientSigningKey && !clientSigningKeySignatureFound) { throw new _errors.InvalidChallengeError("Transaction not signed by the source account of the 'client_domain' " + "ManageData operation"); } if (signersFound.length === 1) { throw new _errors.InvalidChallengeError("None of the given signers match the transaction signatures"); } if (signersFound.length !== tx.signatures.length) { throw new _errors.InvalidChallengeError("Transaction has unrecognized signatures"); } signersFound.splice(signersFound.indexOf(serverKP.publicKey()), 1); if (clientSigningKey) { signersFound.splice(signersFound.indexOf(clientSigningKey), 1); } return signersFound; } function verifyTxSignedBy(transaction, accountID) { return gatherTxSigners(transaction, [accountID]).length !== 0; } function gatherTxSigners(transaction, signers) { var hashedSignatureBase = transaction.hash(); var txSignatures = _toConsumableArray(transaction.signatures); var signersFound = new Set(); var _iterator4 = _createForOfIteratorHelper(signers), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var signer = _step4.value; if (txSignatures.length === 0) { break; } var keypair = void 0; try { keypair = _stellarBase.Keypair.fromPublicKey(signer); } catch (err) { throw new _errors.InvalidChallengeError("Signer is not a valid address: ".concat(err.message)); } for (var i = 0; i < txSignatures.length; i++) { var decSig = txSignatures[i]; if (!decSig.hint().equals(keypair.signatureHint())) { continue; } if (keypair.verify(hashedSignatureBase, decSig.signature())) { signersFound.add(signer); txSignatures.splice(i, 1); break; } } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } return Array.from(signersFound); }