#include <test/jtx.h>

#include <xrpld/core/ConfigSections.h>

#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h>

namespace xrpl {
namespace test {

class MultiSign_test : public beast::unit_test::suite
{
    // Unfunded accounts to use for phantom signing.
    jtx::Account const bogie{"bogie", KeyType::secp256k1};
    jtx::Account const demon{"demon", KeyType::ed25519};
    jtx::Account const ghost{"ghost", KeyType::secp256k1};
    jtx::Account const haunt{"haunt", KeyType::ed25519};
    jtx::Account const jinni{"jinni", KeyType::secp256k1};
    jtx::Account const phase{"phase", KeyType::ed25519};
    jtx::Account const shade{"shade", KeyType::secp256k1};
    jtx::Account const spook{"spook", KeyType::ed25519};
    jtx::Account const acc10{"acc10", KeyType::ed25519};
    jtx::Account const acc11{"acc11", KeyType::ed25519};
    jtx::Account const acc12{"acc12", KeyType::ed25519};
    jtx::Account const acc13{"acc13", KeyType::ed25519};
    jtx::Account const acc14{"acc14", KeyType::ed25519};
    jtx::Account const acc15{"acc15", KeyType::ed25519};
    jtx::Account const acc16{"acc16", KeyType::ed25519};
    jtx::Account const acc17{"acc17", KeyType::ed25519};
    jtx::Account const acc18{"acc18", KeyType::ed25519};
    jtx::Account const acc19{"acc19", KeyType::ed25519};
    jtx::Account const acc20{"acc20", KeyType::ed25519};
    jtx::Account const acc21{"acc21", KeyType::ed25519};
    jtx::Account const acc22{"acc22", KeyType::ed25519};
    jtx::Account const acc23{"acc23", KeyType::ed25519};
    jtx::Account const acc24{"acc24", KeyType::ed25519};
    jtx::Account const acc25{"acc25", KeyType::ed25519};
    jtx::Account const acc26{"acc26", KeyType::ed25519};
    jtx::Account const acc27{"acc27", KeyType::ed25519};
    jtx::Account const acc28{"acc28", KeyType::ed25519};
    jtx::Account const acc29{"acc29", KeyType::ed25519};
    jtx::Account const acc30{"acc30", KeyType::ed25519};
    jtx::Account const acc31{"acc31", KeyType::ed25519};
    jtx::Account const acc32{"acc32", KeyType::ed25519};
    jtx::Account const acc33{"acc33", KeyType::ed25519};

public:
    void
    testNoReserve(FeatureBitset features)
    {
        testcase("No Reserve");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::secp256k1};

        // Pay alice enough to meet the initial reserve, but not enough to
        // meet the reserve for a SignerListSet.
        auto const fee = env.current()->fees().base;
        env.fund(XRP(250) - drops(1), alice);
        env.close();
        env.require(owners(alice, 0));

        {
            // Attach a signer list to alice.  Should fail.
            Json::Value signersList = signers(alice, 1, {{bogie, 1}});
            env(signersList, ter(tecINSUFFICIENT_RESERVE));
            env.close();
            env.require(owners(alice, 0));

            // Fund alice enough to set the signer list, then attach signers.
            env(pay(env.master, alice, fee + drops(1)));
            env.close();
            env(signersList);
            env.close();
            env.require(owners(alice, 1));
        }
        {
            // Pay alice enough to almost make the reserve for the biggest
            // possible list.
            env(pay(env.master, alice, fee - drops(1)));

            // Replace with the biggest possible signer list.  Should fail.
            Json::Value bigSigners = signers(
                alice,
                1,
                {{bogie, 1},
                 {demon, 1},
                 {ghost, 1},
                 {haunt, 1},
                 {jinni, 1},
                 {phase, 1},
                 {shade, 1},
                 {spook, 1}});
            env(bigSigners, ter(tecINSUFFICIENT_RESERVE));
            env.close();
            env.require(owners(alice, 1));

            // Fund alice one more drop (plus the fee) and succeed.
            env(pay(env.master, alice, fee + drops(1)));
            env.close();
            env(bigSigners);
            env.close();
            env.require(owners(alice, 1));
        }
        // Remove alice's signer list and get the owner count back.
        env(signers(alice, jtx::none));
        env.close();
        env.require(owners(alice, 0));
    }

    void
    testSignerListSet(FeatureBitset features)
    {
        testcase("SignerListSet");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();

        // Add alice as a multisigner for herself.  Should fail.
        env(signers(alice, 1, {{alice, 1}}), ter(temBAD_SIGNER));

        // Add a signer with a weight of zero.  Should fail.
        env(signers(alice, 1, {{bogie, 0}}), ter(temBAD_WEIGHT));

        // Add a signer where the weight is too big.  Should fail since
        // the weight field is only 16 bits.  The jtx framework can't do
        // this kind of test, so it's commented out.
        //      env(signers(alice, 1, { { bogie, 0x10000} }), ter
        //      (temBAD_WEIGHT));

        // Add the same signer twice.  Should fail.
        env(signers(
                alice,
                1,
                {{bogie, 1},
                 {demon, 1},
                 {ghost, 1},
                 {haunt, 1},
                 {jinni, 1},
                 {phase, 1},
                 {demon, 1},
                 {spook, 1}}),
            ter(temBAD_SIGNER));

        // Set a quorum of zero.  Should fail.
        env(signers(alice, 0, {{bogie, 1}}), ter(temMALFORMED));

        // Make a signer list where the quorum can't be met.  Should fail.
        env(signers(
                alice,
                9,
                {{bogie, 1},
                 {demon, 1},
                 {ghost, 1},
                 {haunt, 1},
                 {jinni, 1},
                 {phase, 1},
                 {shade, 1},
                 {spook, 1}}),
            ter(temBAD_QUORUM));

        // clang-format off
        // Make a signer list that's too big.  Should fail.
        Account const spare("spare", KeyType::secp256k1);
        env(signers(
                alice,
                1,
                std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
                                          {haunt, 1}, {jinni, 1}, {phase, 1},
                                          {shade, 1}, {spook, 1}, {spare, 1},
                                          {acc10, 1}, {acc11, 1}, {acc12, 1},
                                          {acc13, 1}, {acc14, 1}, {acc15, 1},
                                          {acc16, 1}, {acc17, 1}, {acc18, 1},
                                          {acc19, 1}, {acc20, 1}, {acc21, 1},
                                          {acc22, 1}, {acc23, 1}, {acc24, 1},
                                          {acc25, 1}, {acc26, 1}, {acc27, 1},
                                          {acc28, 1}, {acc29, 1}, {acc30, 1},
                                          {acc31, 1}, {acc32, 1}, {acc33, 1}}),
            ter(temMALFORMED));
        // clang-format on
        env.close();
        env.require(owners(alice, 0));
    }

    void
    testPhantomSigners(FeatureBitset features)
    {
        testcase("Phantom Signers");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();

        // Attach phantom signers to alice and use them for a transaction.
        env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
        env.close();
        env.require(owners(alice, 1));

        // This should work.
        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Either signer alone should work.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(demon), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Duplicate signers should fail.
        aliceSeq = env.seq(alice);
        env(noop(alice),
            msig(demon, demon),
            fee(3 * baseFee),
            rpc("invalidTransaction", "fails local checks: Duplicate Signers not allowed."));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // A non-signer should fail.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie, spook), fee(3 * baseFee), ter(tefBAD_SIGNATURE));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Don't meet the quorum.  Should fail.
        env(signers(alice, 2, {{bogie, 1}, {demon, 1}}));
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie), fee(2 * baseFee), ter(tefBAD_QUORUM));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Meet the quorum.  Should succeed.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
    }

    void
    testFee(FeatureBitset features)
    {
        testcase("Fee");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();

        // Attach maximum possible number of signers to alice.
        env(signers(
            alice,
            1,
            {{bogie, 1},
             {demon, 1},
             {ghost, 1},
             {haunt, 1},
             {jinni, 1},
             {phase, 1},
             {shade, 1},
             {spook, 1}}));
        env.close();
        env.require(owners(alice, 1));

        // This should work.
        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie), fee(2 * baseFee));
        env.close();

        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // This should fail because the fee is too small.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie), fee((2 * baseFee) - 1), ter(telINSUF_FEE_P));
        env.close();

        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // This should work.
        aliceSeq = env.seq(alice);
        env(noop(alice),
            msig(bogie, demon, ghost, haunt, jinni, phase, shade, spook),
            fee(9 * baseFee));
        env.close();

        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // This should fail because the fee is too small.
        aliceSeq = env.seq(alice);
        env(noop(alice),
            msig(bogie, demon, ghost, haunt, jinni, phase, shade, spook),
            fee((9 * baseFee) - 1),
            ter(telINSUF_FEE_P));
        env.close();

        BEAST_EXPECT(env.seq(alice) == aliceSeq);
    }

    void
    testMisorderedSigners(FeatureBitset features)
    {
        testcase("Misordered Signers");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();

        // The signatures in a transaction must be submitted in sorted order.
        // Make sure the transaction fails if they are not.
        env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
        env.close();
        env.require(owners(alice, 1));

        msig phantoms{bogie, demon};
        std::reverse(phantoms.signers.begin(), phantoms.signers.end());
        std::uint32_t const aliceSeq = env.seq(alice);
        env(noop(alice),
            phantoms,
            rpc("invalidTransaction", "fails local checks: Unsorted Signers array."));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);
    }

    void
    testMasterSigners(FeatureBitset features)
    {
        testcase("Master Signers");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        Account const becky{"becky", KeyType::secp256k1};
        Account const cheri{"cheri", KeyType::ed25519};
        env.fund(XRP(1000), alice, becky, cheri);
        env.close();

        // For a different situation, give alice a regular key but don't use it.
        Account const alie{"alie", KeyType::secp256k1};
        env(regkey(alice, alie));
        env.close();
        std::uint32_t aliceSeq = env.seq(alice);
        env(noop(alice), sig(alice));
        env(noop(alice), sig(alie));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);

        // Attach signers to alice
        env(signers(alice, 4, {{becky, 3}, {cheri, 4}}), sig(alice));
        env.close();
        env.require(owners(alice, 1));

        // Attempt a multisigned transaction that meets the quorum.
        auto const baseFee = env.current()->fees().base;
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(cheri), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // If we don't meet the quorum the transaction should fail.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(becky), fee(2 * baseFee), ter(tefBAD_QUORUM));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Give becky and cheri regular keys.
        Account const beck{"beck", KeyType::ed25519};
        env(regkey(becky, beck));
        Account const cher{"cher", KeyType::ed25519};
        env(regkey(cheri, cher));
        env.close();

        // becky's and cheri's master keys should still work.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(becky, cheri), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
    }

    void
    testRegularSigners(FeatureBitset features)
    {
        testcase("Regular Signers");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::secp256k1};
        Account const becky{"becky", KeyType::ed25519};
        Account const cheri{"cheri", KeyType::secp256k1};
        env.fund(XRP(1000), alice, becky, cheri);
        env.close();

        // Attach signers to alice.
        env(signers(alice, 1, {{becky, 1}, {cheri, 1}}), sig(alice));

        // Give everyone regular keys.
        Account const alie{"alie", KeyType::ed25519};
        env(regkey(alice, alie));
        Account const beck{"beck", KeyType::secp256k1};
        env(regkey(becky, beck));
        Account const cher{"cher", KeyType::ed25519};
        env(regkey(cheri, cher));
        env.close();

        // Disable cheri's master key to mix things up.
        env(fset(cheri, asfDisableMaster), sig(cheri));
        env.close();

        // Attempt a multisigned transaction that meets the quorum.
        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = env.seq(alice);
        env(noop(alice), msig(Reg{cheri, cher}), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // cheri should not be able to multisign using her master key.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(cheri), fee(2 * baseFee), ter(tefMASTER_DISABLED));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // becky should be able to multisign using either of her keys.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(becky), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(Reg{becky, beck}), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Both becky and cheri should be able to sign using regular keys.
        aliceSeq = env.seq(alice);
        env(noop(alice), fee(3 * baseFee), msig(Reg{becky, beck}, Reg{cheri, cher}));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
    }

    void
    testRegularSignersUsingSubmitMulti(FeatureBitset features)
    {
        testcase("Regular Signers Using submit_multisigned");

        using namespace jtx;
        Env env(
            *this,
            envconfig([](std::unique_ptr<Config> cfg) {
                cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue");
                return cfg;
            }),
            features);
        Account const alice{"alice", KeyType::secp256k1};
        Account const becky{"becky", KeyType::ed25519};
        Account const cheri{"cheri", KeyType::secp256k1};
        env.fund(XRP(1000), alice, becky, cheri);
        env.close();

        // Attach signers to alice.
        env(signers(alice, 2, {{becky, 1}, {cheri, 1}}), sig(alice));

        // Give everyone regular keys.
        Account const beck{"beck", KeyType::secp256k1};
        env(regkey(becky, beck));
        Account const cher{"cher", KeyType::ed25519};
        env(regkey(cheri, cher));
        env.close();

        // Disable cheri's master key to mix things up.
        env(fset(cheri, asfDisableMaster), sig(cheri));
        env.close();

        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = 0;

        // these represent oft-repeated setup for input json below
        auto setup_tx = [&]() -> Json::Value {
            Json::Value jv;
            jv[jss::tx_json][jss::Account] = alice.human();
            jv[jss::tx_json][jss::TransactionType] = jss::AccountSet;
            jv[jss::tx_json][jss::Fee] = (8 * baseFee).jsonClipped();
            jv[jss::tx_json][jss::Sequence] = env.seq(alice);
            jv[jss::tx_json][jss::SigningPubKey] = "";
            return jv;
        };
        auto cheri_sign = [&](Json::Value& jv) {
            jv[jss::account] = cheri.human();
            jv[jss::key_type] = "ed25519";
            jv[jss::passphrase] = cher.name();
        };
        auto becky_sign = [&](Json::Value& jv) {
            jv[jss::account] = becky.human();
            jv[jss::secret] = beck.name();
        };

        {
            // Attempt a multisigned transaction that meets the quorum.
            // using sign_for and submit_multisigned
            aliceSeq = env.seq(alice);
            Json::Value jv_one = setup_tx();
            cheri_sign(jv_one);
            auto jrr = env.rpc("json", "sign_for", to_string(jv_one))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            // for the second sign_for, use the returned tx_json with
            // first signer info
            Json::Value jv_two;
            jv_two[jss::tx_json] = jrr[jss::tx_json];
            becky_sign(jv_two);
            jrr = env.rpc("json", "sign_for", to_string(jv_two))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            Json::Value jv_submit;
            jv_submit[jss::tx_json] = jrr[jss::tx_json];
            jrr = env.rpc("json", "submit_multisigned", to_string(jv_submit))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");
            env.close();
            BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
        }

        {
            // failure case -- SigningPubKey not empty
            aliceSeq = env.seq(alice);
            Json::Value jv_one = setup_tx();
            jv_one[jss::tx_json][jss::SigningPubKey] = strHex(alice.pk().slice());
            cheri_sign(jv_one);
            auto jrr = env.rpc("json", "sign_for", to_string(jv_one))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "invalidParams");
            BEAST_EXPECT(
                jrr[jss::error_message] ==
                "When multi-signing 'tx_json.SigningPubKey' must be empty.");
        }

        {
            // failure case - bad fee
            aliceSeq = env.seq(alice);
            Json::Value jv_one = setup_tx();
            jv_one[jss::tx_json][jss::Fee] = -1;
            cheri_sign(jv_one);
            auto jrr = env.rpc("json", "sign_for", to_string(jv_one))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            // for the second sign_for, use the returned tx_json with
            // first signer info
            Json::Value jv_two;
            jv_two[jss::tx_json] = jrr[jss::tx_json];
            becky_sign(jv_two);
            jrr = env.rpc("json", "sign_for", to_string(jv_two))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            Json::Value jv_submit;
            jv_submit[jss::tx_json] = jrr[jss::tx_json];
            jrr = env.rpc("json", "submit_multisigned", to_string(jv_submit))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "invalidParams");
            BEAST_EXPECT(
                jrr[jss::error_message] == "Invalid Fee field.  Fees must be greater than zero.");
        }

        {
            // failure case - bad fee v2
            aliceSeq = env.seq(alice);
            Json::Value jv_one = setup_tx();
            jv_one[jss::tx_json][jss::Fee] = alice["USD"](10).value().getFullText();
            cheri_sign(jv_one);
            auto jrr = env.rpc("json", "sign_for", to_string(jv_one))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            // for the second sign_for, use the returned tx_json with
            // first signer info
            Json::Value jv_two;
            jv_two[jss::tx_json] = jrr[jss::tx_json];
            becky_sign(jv_two);
            jrr = env.rpc("json", "sign_for", to_string(jv_two))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            Json::Value jv_submit;
            jv_submit[jss::tx_json] = jrr[jss::tx_json];
            jrr = env.rpc("json", "submit_multisigned", to_string(jv_submit))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "internal");
            BEAST_EXPECT(jrr[jss::error_message] == "Internal error.");
        }

        {
            // cheri should not be able to multisign using her master key.
            aliceSeq = env.seq(alice);
            Json::Value jv = setup_tx();
            jv[jss::account] = cheri.human();
            jv[jss::secret] = cheri.name();
            auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "masterDisabled");
            env.close();
            BEAST_EXPECT(env.seq(alice) == aliceSeq);
        }

        {
            // Unlike cheri, becky should also be able to sign using her master
            // key
            aliceSeq = env.seq(alice);
            Json::Value jv_one = setup_tx();
            cheri_sign(jv_one);
            auto jrr = env.rpc("json", "sign_for", to_string(jv_one))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            // for the second sign_for, use the returned tx_json with
            // first signer info
            Json::Value jv_two;
            jv_two[jss::tx_json] = jrr[jss::tx_json];
            jv_two[jss::account] = becky.human();
            jv_two[jss::key_type] = "ed25519";
            jv_two[jss::passphrase] = becky.name();
            jrr = env.rpc("json", "sign_for", to_string(jv_two))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");

            Json::Value jv_submit;
            jv_submit[jss::tx_json] = jrr[jss::tx_json];
            jrr = env.rpc("json", "submit_multisigned", to_string(jv_submit))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "success");
            env.close();
            BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
        }

        {
            // check for bad or bogus accounts in the tx
            Json::Value jv = setup_tx();
            jv[jss::tx_json][jss::Account] = "DEADBEEF";
            cheri_sign(jv);
            auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "srcActMalformed");

            Account const jimmy{"jimmy"};
            jv[jss::tx_json][jss::Account] = jimmy.human();
            jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "srcActNotFound");
        }

        {
            aliceSeq = env.seq(alice);
            Json::Value jv = setup_tx();
            jv[jss::tx_json][sfSigners.fieldName] = Json::Value{Json::arrayValue};
            becky_sign(jv);
            auto jrr = env.rpc("json", "submit_multisigned", to_string(jv))[jss::result];
            BEAST_EXPECT(jrr[jss::status] == "error");
            BEAST_EXPECT(jrr[jss::error] == "invalidParams");
            BEAST_EXPECT(jrr[jss::error_message] == "tx_json.Signers array may not be empty.");
            env.close();
            BEAST_EXPECT(env.seq(alice) == aliceSeq);
        }
    }

    void
    testHeterogeneousSigners(FeatureBitset features)
    {
        testcase("Heterogeneous Signers");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::secp256k1};
        Account const becky{"becky", KeyType::ed25519};
        Account const cheri{"cheri", KeyType::secp256k1};
        Account const daria{"daria", KeyType::ed25519};
        env.fund(XRP(1000), alice, becky, cheri, daria);
        env.close();

        // alice uses a regular key with the master disabled.
        Account const alie{"alie", KeyType::secp256k1};
        env(regkey(alice, alie));
        env(fset(alice, asfDisableMaster), sig(alice));

        // becky is master only without a regular key.

        // cheri has a regular key, but leaves the master key enabled.
        Account const cher{"cher", KeyType::secp256k1};
        env(regkey(cheri, cher));

        // daria has a regular key and disables her master key.
        Account const dari{"dari", KeyType::ed25519};
        env(regkey(daria, dari));
        env(fset(daria, asfDisableMaster), sig(daria));
        env.close();

        // Attach signers to alice.
        env(signers(alice, 1, {{becky, 1}, {cheri, 1}, {daria, 1}, {jinni, 1}}), sig(alie));
        env.close();
        env.require(owners(alice, 1));

        // Each type of signer should succeed individually.
        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = env.seq(alice);
        env(noop(alice), msig(becky), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(cheri), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(Reg{cheri, cher}), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(Reg{daria, dari}), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(jinni), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        //  Should also work if all signers sign.
        aliceSeq = env.seq(alice);
        env(noop(alice), fee(5 * baseFee), msig(becky, Reg{cheri, cher}, Reg{daria, dari}, jinni));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Require all signers to sign.
        env(signers(
                alice,
                0x3FFFC,
                {{becky, 0xFFFF}, {cheri, 0xFFFF}, {daria, 0xFFFF}, {jinni, 0xFFFF}}),
            sig(alie));
        env.close();
        env.require(owners(alice, 1));

        aliceSeq = env.seq(alice);
        env(noop(alice), fee(9 * baseFee), msig(becky, Reg{cheri, cher}, Reg{daria, dari}, jinni));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Try cheri with both key types.
        aliceSeq = env.seq(alice);
        env(noop(alice), fee(5 * baseFee), msig(becky, cheri, Reg{daria, dari}, jinni));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Makes sure the maximum allowed number of signers works.
        env(signers(
                alice,
                0x7FFF8,
                {{becky, 0xFFFF},
                 {cheri, 0xFFFF},
                 {daria, 0xFFFF},
                 {haunt, 0xFFFF},
                 {jinni, 0xFFFF},
                 {phase, 0xFFFF},
                 {shade, 0xFFFF},
                 {spook, 0xFFFF}}),
            sig(alie));
        env.close();
        env.require(owners(alice, 1));

        aliceSeq = env.seq(alice);
        env(noop(alice),
            fee(9 * baseFee),
            msig(becky, Reg{cheri, cher}, Reg{daria, dari}, haunt, jinni, phase, shade, spook));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // One signer short should fail.
        aliceSeq = env.seq(alice);
        env(noop(alice),
            msig(becky, cheri, haunt, jinni, phase, shade, spook),
            fee(8 * baseFee),
            ter(tefBAD_QUORUM));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Remove alice's signer list and get the owner count back.
        env(signers(alice, jtx::none), sig(alie));
        env.close();
        env.require(owners(alice, 0));
    }

    // We want to always leave an account signable.  Make sure the that we
    // disallow removing the last way a transaction may be signed.
    void
    testKeyDisable(FeatureBitset features)
    {
        testcase("Key Disable");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();

        // There are three negative tests we need to make:
        //  M0. A lone master key cannot be disabled.
        //  R0. A lone regular key cannot be removed.
        //  L0. A lone signer list cannot be removed.
        //
        // Additionally, there are 6 positive tests we need to make:
        //  M1. The master key can be disabled if there's a regular key.
        //  M2. The master key can be disabled if there's a signer list.
        //
        //  R1. The regular key can be removed if there's a signer list.
        //  R2. The regular key can be removed if the master key is enabled.
        //
        //  L1. The signer list can be removed if the master key is enabled.
        //  L2. The signer list can be removed if there's a regular key.

        // Master key tests.
        // M0: A lone master key cannot be disabled.
        env(fset(alice, asfDisableMaster), sig(alice), ter(tecNO_ALTERNATIVE_KEY));

        // Add a regular key.
        Account const alie{"alie", KeyType::ed25519};
        env(regkey(alice, alie));

        // M1: The master key can be disabled if there's a regular key.
        env(fset(alice, asfDisableMaster), sig(alice));

        // R0: A lone regular key cannot be removed.
        env(regkey(alice, disabled), sig(alie), ter(tecNO_ALTERNATIVE_KEY));

        // Add a signer list.
        env(signers(alice, 1, {{bogie, 1}}), sig(alie));

        // R1: The regular key can be removed if there's a signer list.
        env(regkey(alice, disabled), sig(alie));

        // L0: A lone signer list cannot be removed.
        auto const baseFee = env.current()->fees().base;
        env(signers(alice, jtx::none), msig(bogie), fee(2 * baseFee), ter(tecNO_ALTERNATIVE_KEY));

        // Enable the master key.
        env(fclear(alice, asfDisableMaster), msig(bogie), fee(2 * baseFee));

        // L1: The signer list can be removed if the master key is enabled.
        env(signers(alice, jtx::none), msig(bogie), fee(2 * baseFee));

        // Add a signer list.
        env(signers(alice, 1, {{bogie, 1}}), sig(alice));

        // M2: The master key can be disabled if there's a signer list.
        env(fset(alice, asfDisableMaster), sig(alice));

        // Add a regular key.
        env(regkey(alice, alie), msig(bogie), fee(2 * baseFee));

        // L2: The signer list can be removed if there's a regular key.
        env(signers(alice, jtx::none), sig(alie));

        // Enable the master key.
        env(fclear(alice, asfDisableMaster), sig(alie));

        // R2: The regular key can be removed if the master key is enabled.
        env(regkey(alice, disabled), sig(alie));
    }

    // Verify that the first regular key can be made for free using the
    // master key, but not when multisigning.
    void
    testRegKey(FeatureBitset features)
    {
        testcase("Regular Key");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::secp256k1};
        env.fund(XRP(1000), alice);
        env.close();

        // Give alice a regular key with a zero fee.  Should succeed.  Once.
        Account const alie{"alie", KeyType::ed25519};
        env(regkey(alice, alie), sig(alice), fee(0));

        // Try it again and creating the regular key for free should fail.
        Account const liss{"liss", KeyType::secp256k1};
        env(regkey(alice, liss), sig(alice), fee(0), ter(telINSUF_FEE_P));

        // But paying to create a regular key should succeed.
        env(regkey(alice, liss), sig(alice));

        // In contrast, trying to multisign for a regular key with a zero
        // fee should always fail.  Even the first time.
        Account const becky{"becky", KeyType::ed25519};
        env.fund(XRP(1000), becky);
        env.close();

        env(signers(becky, 1, {{alice, 1}}), sig(becky));
        env(regkey(becky, alie), msig(alice), fee(0), ter(telINSUF_FEE_P));

        // Using the master key to sign for a regular key for free should
        // still work.
        env(regkey(becky, alie), sig(becky), fee(0));
    }

    // See if every kind of transaction can be successfully multi-signed.
    void
    testTxTypes(FeatureBitset features)
    {
        testcase("Transaction Types");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::secp256k1};
        Account const becky{"becky", KeyType::ed25519};
        Account const zelda{"zelda", KeyType::secp256k1};
        Account const gw{"gw"};
        auto const USD = gw["USD"];
        env.fund(XRP(1000), alice, becky, zelda, gw);
        env.close();

        // alice uses a regular key with the master disabled.
        Account const alie{"alie", KeyType::secp256k1};
        env(regkey(alice, alie));
        env(fset(alice, asfDisableMaster), sig(alice));

        // Attach signers to alice.
        env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
        env.close();
        env.require(owners(alice, 1));

        // Multisign a ttPAYMENT.
        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = env.seq(alice);
        env(pay(alice, env.master, XRP(1)), msig(becky, bogie), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Multisign a ttACCOUNT_SET.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(becky, bogie), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Multisign a ttREGULAR_KEY_SET.
        aliceSeq = env.seq(alice);
        Account const ace{"ace", KeyType::secp256k1};
        env(regkey(alice, ace), msig(becky, bogie), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Multisign a ttTRUST_SET
        env(trust("alice", USD(100)),
            msig(becky, bogie),
            fee(3 * baseFee),
            require(lines("alice", 1)));
        env.close();
        env.require(owners(alice, 2));

        // Multisign a ttOFFER_CREATE transaction.
        env(pay(gw, alice, USD(50)));
        env.close();
        env.require(balance(alice, USD(50)));
        env.require(balance(gw, alice["USD"](-50)));

        std::uint32_t const offerSeq = env.seq(alice);
        env(offer(alice, XRP(50), USD(50)), msig(becky, bogie), fee(3 * baseFee));
        env.close();
        env.require(owners(alice, 3));

        // Now multisign a ttOFFER_CANCEL canceling the offer we just created.
        {
            aliceSeq = env.seq(alice);
            env(offer_cancel(alice, offerSeq), seq(aliceSeq), msig(becky, bogie), fee(3 * baseFee));
            env.close();
            BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
            env.require(owners(alice, 2));
        }

        // Multisign a ttSIGNER_LIST_SET.
        env(signers(alice, 3, {{becky, 1}, {bogie, 1}, {demon, 1}}),
            msig(becky, bogie),
            fee(3 * baseFee));
        env.close();
        env.require(owners(alice, 2));
    }

    void
    testBadSignatureText(FeatureBitset features)
    {
        testcase("Bad Signature Text");

        // Verify that the text returned for signature failures is correct.
        using namespace jtx;

        Env env{*this, features};

        // lambda that submits an STTx and returns the resulting JSON.
        auto submitSTTx = [&env](STTx const& stx) {
            Json::Value jvResult;
            jvResult[jss::tx_blob] = strHex(stx.getSerializer().slice());
            return env.rpc("json", "submit", to_string(jvResult));
        };

        Account const alice{"alice"};
        env.fund(XRP(1000), alice);
        env.close();
        env(signers(alice, 1, {{bogie, 1}, {demon, 1}}), sig(alice));

        auto const baseFee = env.current()->fees().base;
        {
            // Single-sign, but leave an empty SigningPubKey.
            JTx tx = env.jt(noop(alice), sig(alice));
            STTx local = *(tx.stx);
            local.setFieldVL(sfSigningPubKey, Blob());  // Empty SigningPubKey
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Empty SigningPubKey.");
        }
        {
            // Single-sign, but invalidate the signature.
            JTx tx = env.jt(noop(alice), sig(alice));
            STTx local = *(tx.stx);
            // Flip some bits in the signature.
            auto badSig = local.getFieldVL(sfTxnSignature);
            badSig[20] ^= 0xAA;
            local.setFieldVL(sfTxnSignature, badSig);
            // Signature should fail.
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Invalid signature.");
        }
        {
            // Single-sign, but invalidate the sequence number.
            JTx tx = env.jt(noop(alice), sig(alice));
            STTx local = *(tx.stx);
            // Flip some bits in the signature.
            auto seq = local.getFieldU32(sfSequence);
            local.setFieldU32(sfSequence, seq + 1);
            // Signature should fail.
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Invalid signature.");
        }
        {
            // Multisign, but leave a nonempty sfSigningPubKey.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(bogie));
            STTx local = *(tx.stx);
            local[sfSigningPubKey] = alice.pk();  // Insert sfSigningPubKey
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Cannot both single- and multi-sign.");
        }
        {
            // Both multi- and single-sign with an empty SigningPubKey.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(bogie));
            STTx local = *(tx.stx);
            local.sign(alice.pk(), alice.sk());
            local.setFieldVL(sfSigningPubKey, Blob());  // Empty SigningPubKey
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Cannot both single- and multi-sign.");
        }
        {
            // Multisign but invalidate one of the signatures.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(bogie));
            STTx local = *(tx.stx);
            // Flip some bits in the signature.
            auto& signer = local.peekFieldArray(sfSigners).back();
            auto badSig = signer.getFieldVL(sfTxnSignature);
            badSig[20] ^= 0xAA;
            signer.setFieldVL(sfTxnSignature, badSig);
            // Signature should fail.
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception].asString().find(
                    "Invalid signature on account r") != std::string::npos);
        }
        {
            // Multisign with an empty signers array should fail.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(bogie));
            STTx local = *(tx.stx);
            local.peekFieldArray(sfSigners).clear();  // Empty Signers array.
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Invalid Signers array size.");
        }
        {
            JTx tx = env.jt(
                noop(alice),
                fee(2 * baseFee),

                msig(
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie,
                    bogie));
            STTx local = *(tx.stx);
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Invalid Signers array size.");
        }
        {
            // The account owner may not multisign for themselves.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(alice));
            STTx local = *(tx.stx);
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Invalid multisigner.");
        }
        {
            // No duplicate multisignatures allowed.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(bogie, bogie));
            STTx local = *(tx.stx);
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Duplicate Signers not allowed.");
        }
        {
            // Multisignatures must be submitted in sorted order.
            JTx tx = env.jt(noop(alice), fee(2 * baseFee), msig(bogie, demon));
            STTx local = *(tx.stx);
            // Unsort the Signers array.
            auto& signers = local.peekFieldArray(sfSigners);
            std::reverse(signers.begin(), signers.end());
            // Signature should fail.
            auto const info = submitSTTx(local);
            BEAST_EXPECT(
                info[jss::result][jss::error_exception] ==
                "fails local checks: Unsorted Signers array.");
        }
    }

    void
    testNoMultiSigners(FeatureBitset features)
    {
        testcase("No Multisigners");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        Account const becky{"becky", KeyType::secp256k1};
        env.fund(XRP(1000), alice, becky);
        env.close();

        auto const baseFee = env.current()->fees().base;
        env(noop(alice), msig(becky, demon), fee(3 * baseFee), ter(tefNOT_MULTI_SIGNING));
    }

    void
    testMultisigningMultisigner(FeatureBitset features)
    {
        testcase("Multisigning multisigner");

        // Set up a signer list where one of the signers has both the
        // master disabled and no regular key (because that signer is
        // exclusively multisigning).  That signer should no longer be
        // able to successfully sign the signer list.

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        Account const becky{"becky", KeyType::secp256k1};
        env.fund(XRP(1000), alice, becky);
        env.close();

        // alice sets up a signer list with becky as a signer.
        env(signers(alice, 1, {{becky, 1}}));
        env.close();

        // becky sets up her signer list.
        env(signers(becky, 1, {{bogie, 1}, {demon, 1}}));
        env.close();

        // Because becky has not (yet) disabled her master key, she can
        // multisign a transaction for alice.
        auto const baseFee = env.current()->fees().base;
        env(noop(alice), msig(becky), fee(2 * baseFee));
        env.close();

        // Now becky disables her master key.
        env(fset(becky, asfDisableMaster));
        env.close();

        // Since becky's master key is disabled she can no longer
        // multisign for alice.
        env(noop(alice), msig(becky), fee(2 * baseFee), ter(tefMASTER_DISABLED));
        env.close();

        // Becky cannot 2-level multisign for alice.  2-level multisigning
        // is not supported.
        env(noop(alice), msig(Reg{becky, bogie}), fee(2 * baseFee), ter(tefBAD_SIGNATURE));
        env.close();

        // Verify that becky cannot sign with a regular key that she has
        // not yet enabled.
        Account const beck{"beck", KeyType::ed25519};
        env(noop(alice), msig(Reg{becky, beck}), fee(2 * baseFee), ter(tefBAD_SIGNATURE));
        env.close();

        // Once becky gives herself the regular key, she can sign for alice
        // using that regular key.
        env(regkey(becky, beck), msig(demon), fee(2 * baseFee));
        env.close();

        env(noop(alice), msig(Reg{becky, beck}), fee(2 * baseFee));
        env.close();

        // The presence of becky's regular key does not influence whether she
        // can 2-level multisign; it still won't work.
        env(noop(alice), msig(Reg{becky, demon}), fee(2 * baseFee), ter(tefBAD_SIGNATURE));
        env.close();
    }

    void
    testSignForHash(FeatureBitset features)
    {
        testcase("sign_for Hash");

        // Make sure that the "hash" field returned by the "sign_for" RPC
        // command matches the hash returned when that command is sent
        // through "submit_multisigned".  Make sure that hash also locates
        // the transaction in the ledger.
        using namespace jtx;
        Account const alice{"alice", KeyType::ed25519};

        Env env(
            *this,
            envconfig([](std::unique_ptr<Config> cfg) {
                cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue");
                return cfg;
            }),
            features);
        env.fund(XRP(1000), alice);
        env.close();

        env(signers(alice, 2, {{bogie, 1}, {ghost, 1}}));
        env.close();

        // Use sign_for to sign a transaction where alice pays 10 XRP to
        // masterpassphrase.
        auto const baseFee = env.current()->fees().base;
        Json::Value jvSig1;
        jvSig1[jss::account] = bogie.human();
        jvSig1[jss::secret] = bogie.name();
        jvSig1[jss::tx_json][jss::Account] = alice.human();
        jvSig1[jss::tx_json][jss::Amount] = 10000000;
        jvSig1[jss::tx_json][jss::Destination] = env.master.human();
        jvSig1[jss::tx_json][jss::Fee] = (3 * baseFee).jsonClipped();
        jvSig1[jss::tx_json][jss::Sequence] = env.seq(alice);
        jvSig1[jss::tx_json][jss::TransactionType] = jss::Payment;

        Json::Value jvSig2 = env.rpc("json", "sign_for", to_string(jvSig1));
        BEAST_EXPECT(jvSig2[jss::result][jss::status].asString() == "success");

        // Save the hash with one signature for use later.
        std::string const hash1 = jvSig2[jss::result][jss::tx_json][jss::hash].asString();

        // Add the next signature and sign again.
        jvSig2[jss::result][jss::account] = ghost.human();
        jvSig2[jss::result][jss::secret] = ghost.name();
        Json::Value jvSubmit = env.rpc("json", "sign_for", to_string(jvSig2[jss::result]));
        BEAST_EXPECT(jvSubmit[jss::result][jss::status].asString() == "success");

        // Save the hash with two signatures for use later.
        std::string const hash2 = jvSubmit[jss::result][jss::tx_json][jss::hash].asString();
        BEAST_EXPECT(hash1 != hash2);

        // Submit the result of the two signatures.
        Json::Value jvResult =
            env.rpc("json", "submit_multisigned", to_string(jvSubmit[jss::result]));
        BEAST_EXPECT(jvResult[jss::result][jss::status].asString() == "success");
        BEAST_EXPECT(jvResult[jss::result][jss::engine_result].asString() == "tesSUCCESS");

        // The hash from the submit should be the same as the hash from the
        // second signing.
        BEAST_EXPECT(hash2 == jvResult[jss::result][jss::tx_json][jss::hash].asString());
        env.close();

        // The transaction we just submitted should now be available and
        // validated.
        Json::Value jvTx = env.rpc("tx", hash2);
        BEAST_EXPECT(jvTx[jss::result][jss::status].asString() == "success");
        BEAST_EXPECT(jvTx[jss::result][jss::validated].asString() == "true");
        BEAST_EXPECT(
            jvTx[jss::result][jss::meta][sfTransactionResult.jsonName].asString() == "tesSUCCESS");
    }

    void
    testSignersWithTickets(FeatureBitset features)
    {
        testcase("Signers With Tickets");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(2000), alice);
        env.close();

        // Create a few tickets that alice can use up.
        std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
        env(ticket::create(alice, 20));
        env.close();
        std::uint32_t const aliceSeq = env.seq(alice);

        // Attach phantom signers to alice using a ticket.
        env(signers(alice, 1, {{bogie, 1}, {demon, 1}}), ticket::use(aliceTicketSeq++));
        env.close();
        env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // This should work.
        auto const baseFee = env.current()->fees().base;
        env(noop(alice), msig(bogie, demon), fee(3 * baseFee), ticket::use(aliceTicketSeq++));
        env.close();
        env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Should also be able to remove the signer list using a ticket.
        env(signers(alice, jtx::none), ticket::use(aliceTicketSeq++));
        env.close();
        env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
        BEAST_EXPECT(env.seq(alice) == aliceSeq);
    }

    void
    testSignersWithTags(FeatureBitset features)
    {
        testcase("Signers With Tags");

        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();
        uint8_t tag1[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03,
                          0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                          0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

        uint8_t tag2[] = "hello world some ascii 32b long";  // including 1 byte for NUL

        uint256 bogie_tag = xrpl::base_uint<256>::fromVoid(tag1);
        uint256 demon_tag = xrpl::base_uint<256>::fromVoid(tag2);

        // Attach phantom signers to alice and use them for a transaction.
        env(signers(alice, 1, {{bogie, 1, bogie_tag}, {demon, 1, demon_tag}}));
        env.close();
        env.require(owners(alice, 1));

        // This should work.
        auto const baseFee = env.current()->fees().base;
        std::uint32_t aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Either signer alone should work.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        aliceSeq = env.seq(alice);
        env(noop(alice), msig(demon), fee(2 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);

        // Duplicate signers should fail.
        aliceSeq = env.seq(alice);
        env(noop(alice),
            msig(demon, demon),
            fee(3 * baseFee),
            rpc("invalidTransaction", "fails local checks: Duplicate Signers not allowed."));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // A non-signer should fail.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie, spook), fee(3 * baseFee), ter(tefBAD_SIGNATURE));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Don't meet the quorum.  Should fail.
        env(signers(alice, 2, {{bogie, 1}, {demon, 1}}));
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie), fee(2 * baseFee), ter(tefBAD_QUORUM));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq);

        // Meet the quorum.  Should succeed.
        aliceSeq = env.seq(alice);
        env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
        env.close();
        BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
    }

    void
    testSignerListSetFlags(FeatureBitset features)
    {
        using namespace test::jtx;

        Env env{*this, features};
        Account const alice{"alice"};

        env.fund(XRP(1000), alice);
        env.close();

        bool const enabled = features[fixInvalidTxFlags];
        testcase(std::string("SignerListSet flag, fix ") + (enabled ? "enabled" : "disabled"));

        ter const expected(enabled ? TER(temINVALID_FLAG) : TER(tesSUCCESS));
        env(signers(alice, 2, {{bogie, 1}, {ghost, 1}}), expected, txflags(tfPassive));
        env.close();
    }

    void
    testSignerListObject(FeatureBitset features)
    {
        testcase("SignerList Object");

        // Verify that the SignerList object is created correctly.
        using namespace jtx;
        Env env{*this, features};
        Account const alice{"alice", KeyType::ed25519};
        env.fund(XRP(1000), alice);
        env.close();

        // Attach phantom signers to alice.
        env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
        env.close();

        // Verify that the SignerList object was created correctly.
        auto const& sle = env.le(keylet::signers(alice.id()));
        BEAST_EXPECT(sle);
        BEAST_EXPECT(sle->getFieldArray(sfSignerEntries).size() == 2);
        if (features[fixIncludeKeyletFields])
        {
            BEAST_EXPECT((*sle)[sfOwner] == alice.id());
        }
        else
        {
            BEAST_EXPECT(!sle->isFieldPresent(sfOwner));
        }
    }

    void
    testAll(FeatureBitset features)
    {
        testNoReserve(features);
        testSignerListSet(features);
        testPhantomSigners(features);
        testFee(features);
        testMisorderedSigners(features);
        testMasterSigners(features);
        testRegularSigners(features);
        testRegularSignersUsingSubmitMulti(features);
        testHeterogeneousSigners(features);
        testKeyDisable(features);
        testRegKey(features);
        testTxTypes(features);
        testBadSignatureText(features);
        testNoMultiSigners(features);
        testMultisigningMultisigner(features);
        testSignForHash(features);
        testSignersWithTickets(features);
        testSignersWithTags(features);
    }

    void
    run() override
    {
        using namespace jtx;
        auto const all = testable_amendments();

        testAll(all);

        testSignerListSetFlags(all - fixInvalidTxFlags);
        testSignerListSetFlags(all);

        testSignerListObject(all - fixIncludeKeyletFields);
        testSignerListObject(all);
    }
};

BEAST_DEFINE_TESTSUITE(MultiSign, app, xrpl);

}  // namespace test
}  // namespace xrpl
