xdb-digitalbits-sdk
Version:
xdb-digitalbits-sdk is a library for working with the DigitalBits Frontier server.
230 lines • 11.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Utils = void 0;
var tslib_1 = require("tslib");
var clone_1 = tslib_1.__importDefault(require("lodash/clone"));
var randombytes_1 = tslib_1.__importDefault(require("randombytes"));
var xdb_digitalbits_base_1 = require("xdb-digitalbits-base");
var errors_1 = require("./errors");
var Utils;
(function (Utils) {
function buildChallengeTx(serverKeypair, clientAccountID, homeDomain, timeout, networkPassphrase, webAuthDomain) {
if (timeout === void 0) { timeout = 300; }
if (clientAccountID.startsWith("M")) {
throw Error("Invalid clientAccountID: multiplexed accounts are not supported.");
}
var account = new xdb_digitalbits_base_1.Account(serverKeypair.publicKey(), "-1");
var now = Math.floor(Date.now() / 1000);
var value = randombytes_1.default(48).toString("base64");
var transaction = new xdb_digitalbits_base_1.TransactionBuilder(account, {
fee: xdb_digitalbits_base_1.BASE_FEE,
networkPassphrase: networkPassphrase,
timebounds: {
minTime: now,
maxTime: now + timeout,
},
})
.addOperation(xdb_digitalbits_base_1.Operation.manageData({
name: homeDomain + " auth",
value: value,
source: clientAccountID,
}))
.addOperation(xdb_digitalbits_base_1.Operation.manageData({
name: "web_auth_domain",
value: webAuthDomain,
source: account.accountId(),
}))
.build();
transaction.sign(serverKeypair);
return transaction
.toEnvelope()
.toXDR("base64")
.toString();
}
Utils.buildChallengeTx = buildChallengeTx;
function readChallengeTx(challengeTx, serverAccountID, networkPassphrase, homeDomains, webAuthDomain) {
var _a;
if (serverAccountID.startsWith("M")) {
throw Error("Invalid serverAccountID: multiplexed accounts are not supported.");
}
var transaction = xdb_digitalbits_base_1.TransactionBuilder.fromXDR(challengeTx, networkPassphrase);
if (!(transaction instanceof xdb_digitalbits_base_1.Transaction)) {
throw new errors_1.InvalidSep10ChallengeError("Invalid challenge: expected a Transaction but received a FeeBumpTransaction");
}
var sequence = Number.parseInt(transaction.sequence, 10);
if (sequence !== 0) {
throw new errors_1.InvalidSep10ChallengeError("The transaction sequence number should be zero");
}
if (transaction.source !== serverAccountID) {
throw new errors_1.InvalidSep10ChallengeError("The transaction source account is not equal to the server's account");
}
if (transaction.operations.length < 1) {
throw new errors_1.InvalidSep10ChallengeError("The transaction should contain at least one operation");
}
var _b = transaction.operations, operation = _b[0], subsequentOperations = _b.slice(1);
if (!operation.source) {
throw new errors_1.InvalidSep10ChallengeError("The transaction's operation should contain a source account");
}
var clientAccountID = operation.source;
if (operation.type !== "manageData") {
throw new errors_1.InvalidSep10ChallengeError("The transaction's operation type should be 'manageData'");
}
if (transaction.timeBounds &&
Number.parseInt((_a = transaction.timeBounds) === null || _a === void 0 ? void 0 : _a.maxTime, 10) === xdb_digitalbits_base_1.TimeoutInfinite) {
throw new errors_1.InvalidSep10ChallengeError("The transaction requires non-infinite timebounds");
}
if (!validateTimebounds(transaction)) {
throw new errors_1.InvalidSep10ChallengeError("The transaction has expired");
}
if (operation.value === undefined) {
throw new errors_1.InvalidSep10ChallengeError("The transaction's operation values should not be null");
}
if (!operation.value) {
throw new errors_1.InvalidSep10ChallengeError("The transaction's operation value should not be null");
}
if (Buffer.from(operation.value.toString(), "base64").length !== 48) {
throw new errors_1.InvalidSep10ChallengeError("The transaction's operation value should be a 64 bytes base64 random string");
}
if (!homeDomains) {
throw new errors_1.InvalidSep10ChallengeError("Invalid homeDomains: a home domain must be provided for verification");
}
var matchedHomeDomain;
if (typeof homeDomains === "string") {
if (homeDomains + " auth" === operation.name) {
matchedHomeDomain = homeDomains;
}
}
else if (Array.isArray(homeDomains)) {
matchedHomeDomain = homeDomains.find(function (domain) { return domain + " auth" === operation.name; });
}
else {
throw new errors_1.InvalidSep10ChallengeError("Invalid homeDomains: homeDomains type is " + typeof homeDomains + " but should be a string or an array");
}
if (!matchedHomeDomain) {
throw new errors_1.InvalidSep10ChallengeError("Invalid homeDomains: the transaction's operation key name does not match the expected home domain");
}
for (var _i = 0, subsequentOperations_1 = subsequentOperations; _i < subsequentOperations_1.length; _i++) {
var op = subsequentOperations_1[_i];
if (op.type !== "manageData") {
throw new errors_1.InvalidSep10ChallengeError("The transaction has operations that are not of type 'manageData'");
}
if (op.source !== serverAccountID) {
throw new errors_1.InvalidSep10ChallengeError("The transaction has operations that are unrecognized");
}
if (op.name === "web_auth_domain") {
if (op.value === undefined) {
throw new errors_1.InvalidSep10ChallengeError("'web_auth_domain' operation value should not be null");
}
if (op.value.compare(Buffer.from(webAuthDomain))) {
throw new errors_1.InvalidSep10ChallengeError("'web_auth_domain' operation value does not match " + webAuthDomain);
}
}
}
return { tx: transaction, clientAccountID: clientAccountID, matchedHomeDomain: matchedHomeDomain };
}
Utils.readChallengeTx = readChallengeTx;
function verifyChallengeTxThreshold(challengeTx, serverAccountID, networkPassphrase, threshold, signerSummary, homeDomains, webAuthDomain) {
var _a;
var signers = signerSummary.map(function (signer) { return signer.key; });
var signersFound = verifyChallengeTxSigners(challengeTx, serverAccountID, networkPassphrase, signers, homeDomains, webAuthDomain);
var weight = 0;
var _loop_1 = function (signer) {
var sigWeight = ((_a = signerSummary.find(function (s) { return s.key === signer; })) === null || _a === void 0 ? void 0 : _a.weight) || 0;
weight += sigWeight;
};
for (var _i = 0, signersFound_1 = signersFound; _i < signersFound_1.length; _i++) {
var signer = signersFound_1[_i];
_loop_1(signer);
}
if (weight < threshold) {
throw new errors_1.InvalidSep10ChallengeError("signers with weight " + weight + " do not meet threshold " + threshold + "\"");
}
return signersFound;
}
Utils.verifyChallengeTxThreshold = verifyChallengeTxThreshold;
function verifyChallengeTxSigners(challengeTx, serverAccountID, networkPassphrase, signers, homeDomains, webAuthDomain) {
var tx = readChallengeTx(challengeTx, serverAccountID, networkPassphrase, homeDomains, webAuthDomain).tx;
var serverKP;
try {
serverKP = xdb_digitalbits_base_1.Keypair.fromPublicKey(serverAccountID);
}
catch (err) {
throw new Error("Couldn't infer keypair from the provided 'serverAccountID': " +
err.message);
}
var clientSigners = new Set();
for (var _i = 0, signers_1 = signers; _i < signers_1.length; _i++) {
var signer = signers_1[_i];
if (signer === serverKP.publicKey()) {
continue;
}
if (signer.charAt(0) !== "G") {
continue;
}
clientSigners.add(signer);
}
if (clientSigners.size === 0) {
throw new errors_1.InvalidSep10ChallengeError("No verifiable client signers provided, at least one G... address must be provided");
}
var allSigners = tslib_1.__spreadArrays([
serverKP.publicKey()
], Array.from(clientSigners));
var signersFound = gatherTxSigners(tx, allSigners);
if (signersFound.indexOf(serverKP.publicKey()) === -1) {
throw new errors_1.InvalidSep10ChallengeError("Transaction not signed by server: '" + serverKP.publicKey() + "'");
}
if (signersFound.length === 1) {
throw new errors_1.InvalidSep10ChallengeError("None of the given signers match the transaction signatures");
}
if (signersFound.length !== tx.signatures.length) {
throw new errors_1.InvalidSep10ChallengeError("Transaction has unrecognized signatures");
}
signersFound.splice(signersFound.indexOf(serverKP.publicKey()), 1);
return signersFound;
}
Utils.verifyChallengeTxSigners = verifyChallengeTxSigners;
function verifyTxSignedBy(transaction, accountID) {
return gatherTxSigners(transaction, [accountID]).length !== 0;
}
Utils.verifyTxSignedBy = verifyTxSignedBy;
function gatherTxSigners(transaction, signers) {
var hashedSignatureBase = transaction.hash();
var txSignatures = clone_1.default(transaction.signatures);
var signersFound = new Set();
for (var _i = 0, signers_2 = signers; _i < signers_2.length; _i++) {
var signer = signers_2[_i];
if (txSignatures.length === 0) {
break;
}
var keypair = void 0;
try {
keypair = xdb_digitalbits_base_1.Keypair.fromPublicKey(signer);
}
catch (err) {
throw new errors_1.InvalidSep10ChallengeError("Signer is not a valid address: " + 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;
}
}
}
return Array.from(signersFound);
}
Utils.gatherTxSigners = gatherTxSigners;
function validateTimebounds(transaction) {
if (!transaction.timeBounds) {
return false;
}
var now = Math.floor(Date.now() / 1000);
var _a = transaction.timeBounds, minTime = _a.minTime, maxTime = _a.maxTime;
return (now >= Number.parseInt(minTime, 10) && now <= Number.parseInt(maxTime, 10));
}
})(Utils = exports.Utils || (exports.Utils = {}));
//# sourceMappingURL=utils.js.map