Sponsor signing

This commit is contained in:
tequ
2025-09-07 22:11:15 +09:00
parent 9ff71aa109
commit 02d8f9fbef
16 changed files with 501 additions and 122 deletions

View File

@@ -91,6 +91,9 @@ enum class HashPrefix : std::uint32_t {
/** Batch */
batch = detail::make_hash_prefix('B', 'C', 'H'),
/** Sponsor */
sponsor = detail::make_hash_prefix('S', 'P', 'N'),
};
template <class Hasher>

View File

@@ -112,6 +112,12 @@ public:
boost::container::flat_set<AccountID>
getMentionedAccounts() const;
static Blob
getSigningData(STTx const& that);
static Blob
getSponsorSigningData(STTx const& that);
uint256
getTransactionID() const;
@@ -138,6 +144,11 @@ public:
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
Expected<void, std::string>
checkSponsorSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
// SQL Functions with metadata.
static std::string const&
getMetaSQLInsertReplaceHeader();
@@ -170,12 +181,23 @@ private:
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig) const;
Expected<void, std::string>
checkSponsorSingleSign(
STObject const& signer,
RequireFullyCanonicalSig requireCanonicalSig) const;
Expected<void, std::string>
checkBatchMultiSign(
STObject const& batchSigner,
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
Expected<void, std::string>
checkSponsorMultiSign(
STObject const& signer,
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
@@ -224,6 +246,46 @@ STTx::getTransactionID() const
return tid_;
}
/** Return a Serializer suitable for computing a multisigning TxnSignature. */
Serializer
buildMultiSigningData(STObject const& obj, AccountID const& signingID);
/** Break the multi-signing hash computation into 2 parts for optimization.
We can optimize verifying multiple multisignatures by splitting the
data building into two parts;
o A large part that is shared by all of the computations.
o A small part that is unique to each signer in the multisignature.
The following methods support that optimization:
1. startMultiSigningData provides the large part which can be shared.
2. finishMultiSigningData caps the passed in serializer with each
signer's unique data.
*/
Serializer
startMultiSigningData(STObject const& obj);
inline void
finishMultiSigningData(AccountID const& signingID, Serializer& s)
{
s.addBitString(signingID);
}
Serializer
buildSponsorMultiSigningData(
STObject const& obj,
AccountID const& signingID,
uint32_t flags);
Serializer
startSponsorSigningData(STObject const& obj);
inline void
finishSponsorSigningData(AccountID const& signerID, Serializer& s)
{
s.addBitString(signerID);
}
} // namespace ripple
#endif

View File

@@ -61,31 +61,6 @@ verify(
PublicKey const& pk,
SF_VL const& sigField = sfSignature);
/** Return a Serializer suitable for computing a multisigning TxnSignature. */
Serializer
buildMultiSigningData(STObject const& obj, AccountID const& signingID);
/** Break the multi-signing hash computation into 2 parts for optimization.
We can optimize verifying multiple multisignatures by splitting the
data building into two parts;
o A large part that is shared by all of the computations.
o A small part that is unique to each signer in the multisignature.
The following methods support that optimization:
1. startMultiSigningData provides the large part which can be shared.
2. finishMultiSigningData caps the passed in serializer with each
signer's unique data.
*/
Serializer
startMultiSigningData(STObject const& obj);
inline void
finishMultiSigningData(AccountID const& signingID, Serializer& s)
{
s.addBitString(signingID);
}
} // namespace ripple
#endif

View File

@@ -0,0 +1,35 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Serializer.h>
namespace ripple {
inline void
addSerializeSponsorData(
Serializer& msg,
AccountID const& sponsorID,
std::uint32_t const& flags)
{
msg.addBitString(sponsorID);
msg.add32(flags);
}
} // namespace ripple

View File

@@ -366,7 +366,7 @@ UNTYPED_SFIELD(sfCredential, OBJECT, 33)
UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfSponsor, OBJECT, 37)
UNTYPED_SFIELD(sfSponsor, OBJECT, 37, SField::sMD_Default, SField::notSigning)
// array of objects (common)
// ARRAY/1 is reserved for end of array

View File

@@ -49,6 +49,7 @@
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/Sponsor.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/jss.h>
@@ -184,15 +185,26 @@ STTx::getMentionedAccounts() const
return list;
}
static Blob
getSigningData(STTx const& that)
Blob
STTx::getSigningData(STTx const& that)
{
Serializer s;
s.add32(HashPrefix::txSign);
that.addWithoutSigningFields(s);
if (that.isFieldPresent(sfSponsor))
{
auto const sst = that.getFieldObject(sfSponsor);
addSerializeSponsorData(s, sst.getAccountID(sfAccount), sst.getFlags());
}
return s.getData();
}
Blob
STTx::getSponsorSigningData(STTx const& that)
{
return startSponsorSigningData(that).getData();
}
uint256
STTx::getSigningHash() const
{
@@ -238,7 +250,7 @@ STTx::getFeePayer() const
{
if (isFieldPresent(sfSponsor))
{
if (getFieldObject(sfSponsor)[sfFlags] & tfSponsorFee)
if (getFieldObject(sfSponsor).isFlag(tfSponsorFee))
{
return getFieldObject(sfSponsor)[sfAccount];
}
@@ -320,6 +332,42 @@ STTx::checkBatchSign(
return Unexpected("Internal batch signature check failure.");
}
Expected<void, std::string>
STTx::checkSponsorSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
try
{
XRPL_ASSERT(
isFieldPresent(sfSponsor),
"STTx::checkSponsorSign: not a sponsored transaction");
if (!isFieldPresent(sfSponsor))
{
JLOG(debugLog().fatal()) << "not a sponsored transaction";
return Unexpected("Not a sponsored transaction.");
}
STObject const& sponsorObj{getFieldObject(sfSponsor)};
Blob const& signingPubKey = sponsorObj.getFieldVL(sfSigningPubKey);
auto const result = signingPubKey.empty()
? checkSponsorMultiSign(sponsorObj, requireCanonicalSig, rules)
: checkSponsorSingleSign(sponsorObj, requireCanonicalSig);
if (!result)
return result;
return {};
}
catch (std::exception const& e)
{
JLOG(debugLog().error())
<< "Sponsor signature check failed: " << e.what();
}
return Unexpected("Sponsor signature check failure.");
}
Json::Value
STTx::getJson(JsonOptions options) const
{
@@ -457,6 +505,17 @@ STTx::checkBatchSingleSign(
return singleSignHelper(batchSigner, msg.slice(), fullyCanonical);
}
Expected<void, std::string>
STTx::checkSponsorSingleSign(
STObject const& signer,
RequireFullyCanonicalSig requireCanonicalSig) const
{
auto const data = getSponsorSigningData(*this);
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);
return singleSignHelper(signer, makeSlice(data), fullyCanonical);
}
Expected<void, std::string>
multiSignHelper(
STObject const& signerObj,
@@ -465,9 +524,9 @@ multiSignHelper(
Rules const& rules)
{
// Make sure the MultiSigners are present. Otherwise they are not
// attempting multi-signing and we just have a bad SigningPubKey.
// attempting multi-signing and we just have a bad Signers.
if (!signerObj.isFieldPresent(sfSigners))
return Unexpected("Empty SigningPubKey.");
return Unexpected("Empty Signers.");
// We don't allow both an sfSigners and an sfTxnSignature. Both fields
// being present would indicate that the transaction is signed both ways.
@@ -560,6 +619,32 @@ STTx::checkBatchMultiSign(
rules);
}
Expected<void, std::string>
STTx::checkSponsorMultiSign(
STObject const& sponsorObj,
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature.
auto const data = startSponsorSigningData(*this);
Serializer dataStart = Serializer(data.data(), data.size());
return multiSignHelper(
sponsorObj,
fullyCanonical,
[&dataStart](AccountID const& accountID) mutable -> Serializer {
Serializer s = dataStart;
finishSponsorSigningData(accountID, s);
return s;
},
rules);
}
Expected<void, std::string>
STTx::checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
@@ -848,4 +933,90 @@ isPseudoTx(STObject const& tx)
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY;
}
// Questions regarding buildMultiSigningData:
//
// Why do we include the Signer.Account in the blob to be signed?
//
// Unless you include the Account which is signing in the signing blob,
// you could swap out any Signer.Account for any other, which may also
// be on the SignerList and have a RegularKey matching the
// Signer.SigningPubKey.
//
// That RegularKey may be set to allow some 3rd party to sign transactions
// on the account's behalf, and that RegularKey could be common amongst all
// users of the 3rd party. That's just one example of sharing the same
// RegularKey amongst various accounts and just one vulnerability.
//
// "When you have something that's easy to do that makes entire classes of
// attacks clearly and obviously impossible, you need a damn good reason
// not to do it." -- David Schwartz
//
// Why would we include the signingFor account in the blob to be signed?
//
// In the current signing scheme, the account that a signer is `signing
// for/on behalf of` is the tx_json.Account.
//
// Later we might support more levels of signing. Suppose Bob is a signer
// for Alice, and Carol is a signer for Bob, so Carol can sign for Bob who
// signs for Alice. But suppose Alice has two signers: Bob and Dave. If
// Carol is a signer for both Bob and Dave, then the signature needs to
// distinguish between Carol signing for Bob and Carol signing for Dave.
//
// So, if we support multiple levels of signing, then we'll need to
// incorporate the "signing for" accounts into the signing data as well.
Serializer
buildMultiSigningData(STObject const& obj, AccountID const& signingID)
{
Serializer s{startMultiSigningData(obj)};
finishMultiSigningData(signingID, s);
return s;
}
Serializer
startMultiSigningData(STObject const& obj)
{
Serializer s;
s.add32(HashPrefix::txMultiSign);
obj.addWithoutSigningFields(s);
// if (obj.isFieldPresent(sfSponsor))
// {
// auto const sst = obj.getFieldObject(sfSponsor);
// addSerializeSponsorData(s, sst.getAccountID(sfAccount),
// sst.getFlags());
// }
return s;
}
Serializer
buildSponsorMultiSigningData(
STObject const& obj,
AccountID const& signingID,
uint32_t flags)
{
Serializer s{startSponsorSigningData(obj)};
finishSponsorSigningData(signingID, s);
return s;
}
Serializer
startSponsorSigningData(STObject const& obj)
{
Serializer s;
s.add32(HashPrefix::sponsor);
STObject tmp = obj;
tmp.setFieldVL(sfSigningPubKey, Blob{});
tmp.addWithoutSigningFields(s);
XRPL_ASSERT(
tmp.isFieldPresent(sfSponsor),
"STTx::getSponsorSigningData : sponsor is not set");
if (tmp.isFieldPresent(sfSponsor))
{
auto const sst = tmp.getFieldObject(sfSponsor);
addSerializeSponsorData(s, sst.getAccountID(sfAccount), sst.getFlags());
}
return s;
}
} // namespace ripple

View File

@@ -61,52 +61,4 @@ verify(
pk, Slice(ss.data(), ss.size()), Slice(sig->data(), sig->size()));
}
// Questions regarding buildMultiSigningData:
//
// Why do we include the Signer.Account in the blob to be signed?
//
// Unless you include the Account which is signing in the signing blob,
// you could swap out any Signer.Account for any other, which may also
// be on the SignerList and have a RegularKey matching the
// Signer.SigningPubKey.
//
// That RegularKey may be set to allow some 3rd party to sign transactions
// on the account's behalf, and that RegularKey could be common amongst all
// users of the 3rd party. That's just one example of sharing the same
// RegularKey amongst various accounts and just one vulnerability.
//
// "When you have something that's easy to do that makes entire classes of
// attacks clearly and obviously impossible, you need a damn good reason
// not to do it." -- David Schwartz
//
// Why would we include the signingFor account in the blob to be signed?
//
// In the current signing scheme, the account that a signer is `signing
// for/on behalf of` is the tx_json.Account.
//
// Later we might support more levels of signing. Suppose Bob is a signer
// for Alice, and Carol is a signer for Bob, so Carol can sign for Bob who
// signs for Alice. But suppose Alice has two signers: Bob and Dave. If
// Carol is a signer for both Bob and Dave, then the signature needs to
// distinguish between Carol signing for Bob and Carol signing for Dave.
//
// So, if we support multiple levels of signing, then we'll need to
// incorporate the "signing for" accounts into the signing data as well.
Serializer
buildMultiSigningData(STObject const& obj, AccountID const& signingID)
{
Serializer s{startMultiSigningData(obj)};
finishMultiSigningData(signingID, s);
return s;
}
Serializer
startMultiSigningData(STObject const& obj)
{
Serializer s;
s.add32(HashPrefix::txMultiSign);
obj.addWithoutSigningFields(s);
return s;
}
} // namespace ripple

View File

@@ -51,6 +51,112 @@ public:
env(sponsor::transfer(alice), ter(temDISABLED));
}
void
testSingleSigning()
{
testcase("Single signing");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const invalid("invalid");
env.fund(XRP(10000), alice, sponsor);
env.close();
// Signature doesn't exist
auto tx = noop(alice);
tx[sfSponsor.jsonName][sfAccount.jsonName] = sponsor.human();
tx[sfSponsor.jsonName][sfSigningPubKey.jsonName] =
strHex(sponsor.pk().slice());
env(tx,
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
ter(telENV_RPC_FAILED));
// Invalid signature
tx[sfSponsor.jsonName][sfTxnSignature.jsonName] = "DEADBEEF";
env(tx,
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
ter(telENV_RPC_FAILED));
// Signer account doesn't exist
env(noop(alice),
fee(XRP(1)),
sponsor::as(invalid, tfSponsorReserve),
sponsor::sig(invalid),
ter(tefBAD_AUTH));
// Success
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor),
ter(tesSUCCESS));
}
void
testMultiSigning()
{
testcase("Multi signing");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const sponsor("sponsor");
Account const invalid("invalid");
Account const signer1("signer1");
Account const signer2("signer2");
env.fund(XRP(10000), alice, sponsor);
env.close();
env(signers(sponsor, 1, {{signer1, 1}, {signer2, 1}}));
env.close();
// Signature doesn't exist
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
ter(telENV_RPC_FAILED));
// Invalid signature
auto tx = noop(alice);
auto& signers1 =
tx[sfSponsor.jsonName][sfSigners.jsonName][0U][sfSigner.jsonName];
signers1[sfAccount.jsonName] = signer1.human();
signers1[sfSigningPubKey.jsonName] = strHex(signer1.pk().slice());
signers1[sfTxnSignature.jsonName] = "DEADBEEF";
env(tx,
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
ter(telENV_RPC_FAILED));
// Signer account doesn't exist
env(noop(alice),
fee(XRP(1)),
sponsor::as(invalid, tfSponsorReserve),
sponsor::msig({signer1}),
ter(tefBAD_AUTH));
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::msig({signer1}),
ter(tesSUCCESS));
env(signers(sponsor, 2, {{signer1, 1}, {signer2, 1}}));
env.close();
env(noop(alice),
fee(XRP(1)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::msig({signer1, signer2}),
ter(tesSUCCESS));
}
void
testTransferSponsor()
{
@@ -658,6 +764,8 @@ public:
run() override
{
testDisabled();
testSingleSigning();
testMultiSigning();
testTransferSponsor();
testSponsorFee();
testSponsorAccount();

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_TEST_JTX_JTX_H_INCLUDED
#define RIPPLE_TEST_JTX_JTX_H_INCLUDED
#include <test/jtx/Account.h>
#include <test/jtx/basic_prop.h>
#include <test/jtx/requires.h>
@@ -55,6 +56,7 @@ struct JTx
bool fill_netid = true;
std::shared_ptr<STTx const> stx;
std::function<void(Env&, JTx&)> signer;
std::function<void(Env&, JTx&)> sponsorSigner;
JTx() = default;
JTx(JTx const&) = default;

View File

@@ -567,6 +567,9 @@ Env::autofill_sig(JTx& jt)
jtx::sign(jv, lookup(ar->getAccountID(sfRegularKey)));
else
jtx::sign(jv, account);
if (jt.sponsorSigner)
jt.sponsorSigner(*this, jt);
}
void

View File

@@ -21,6 +21,7 @@
#include <test/jtx/utility.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/Sponsor.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -50,61 +51,70 @@ as::operator()(Env& env, JTx& jt) const
void
sig::operator()(Env& env, JTx& jt) const
{
std::optional<STObject> st;
try
{
// required to cast the STObject to STTx
jt.jv[jss::SigningPubKey] = "";
st = parse(jt.jv);
}
catch (parse_error const&)
{
env.test.log << pretty(jt.jv) << std::endl;
Rethrow();
}
auto const signer = signer_;
jt.jv[sfSponsor.jsonName][sfAccount.jsonName] = signer.acct.human();
jt.jv[sfSponsor.jsonName][sfSigningPubKey.jsonName] =
strHex(signer.sig.pk().slice());
Serializer ss;
ss.add32(HashPrefix::txSign);
parse(jt.jv).addWithoutSigningFields(ss);
auto const sig =
ripple::sign(signer.acct.pk(), signer.acct.sk(), ss.slice());
jt.jv[sfSponsor.jsonName][jss::TxnSignature] =
strHex(Slice{sig.data(), sig.size()});
}
void
msig::operator()(Env& env, JTx& jt) const
{
auto const mySigners = signers;
jt.signer = [mySigners, &env](Env&, JTx& jtx) {
jtx[sfSponsor.getJsonName()][sfSigningPubKey.getJsonName()] = "";
jt.sponsorSigner = [signer, &env](Env&, JTx& jtx) {
std::optional<STObject> st;
try
{
st = parse(jtx.jv);
Json::Value jv = jtx.jv;
st = parse(jv);
}
catch (parse_error const&)
{
env.test.log << pretty(jtx.jv) << std::endl;
Rethrow();
}
auto& js = jtx[sfSponsor.getJsonName()][sfSigners.getJsonName()];
auto const sst = st->getFieldObject(sfSponsor);
auto const signingData =
STTx::getSponsorSigningData(STTx{std::move(*st)});
auto const sig = ripple::sign(
signer.acct.pk(), signer.acct.sk(), makeSlice(signingData));
jtx.jv[sfSponsor.jsonName][jss::TxnSignature] =
strHex(Slice{sig.data(), sig.size()});
};
}
void
msig::operator()(Env& env, JTx& jt) const
{
jt.jv[sfSponsor.jsonName][sfSigningPubKey.jsonName] = "";
auto const mySigners = signers;
jt.sponsorSigner = [mySigners, &env](Env&, JTx& jtx) {
std::optional<STObject> st;
try
{
Json::Value jv = jtx.jv;
jv[jss::SigningPubKey] = "";
st = parse(jv);
}
catch (parse_error const&)
{
env.test.log << pretty(jtx.jv) << std::endl;
Rethrow();
}
auto const sst = st->getFieldObject(sfSponsor);
auto& js = jtx[sfSponsor.jsonName][sfSigners.jsonName];
for (std::size_t i = 0; i < mySigners.size(); ++i)
{
auto const& e = mySigners[i];
auto& jo = js[i][sfSigner.getJsonName()];
auto& jo = js[i][sfSigner.jsonName];
jo[jss::Account] = e.acct.human();
jo[jss::SigningPubKey] = strHex(e.sig.pk().slice());
Serializer ss{buildMultiSigningData(*st, e.acct.id())};
Serializer ss{
buildSponsorMultiSigningData(*st, e.acct.id(), sst.getFlags())};
auto const sig = ripple::sign(
*publicKeyType(e.sig.pk().slice()), e.sig.sk(), ss.slice());
jo[sfTxnSignature.getJsonName()] =
strHex(Slice{sig.data(), sig.size()});
jo[sfTxnSignature.jsonName] = strHex(Slice{sig.data(), sig.size()});
}
};
}

View File

@@ -47,10 +47,8 @@ void
sign(Json::Value& jv, Account const& account)
{
jv[jss::SigningPubKey] = strHex(account.pk().slice());
Serializer ss;
ss.add32(HashPrefix::txSign);
parse(jv).addWithoutSigningFields(ss);
auto const sig = ripple::sign(account.pk(), account.sk(), ss.slice());
auto const blob = STTx::getSigningData(STTx{parse(jv)});
auto const sig = ripple::sign(account.pk(), account.sk(), makeSlice(blob));
jv[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
}

View File

@@ -54,10 +54,10 @@ public:
struct sig
{
private:
Reg signer;
Reg signer_;
public:
sig(Reg signer_) : signer(std::move(signer_))
sig(Reg signer) : signer_(std::move(signer))
{
}

View File

@@ -647,10 +647,11 @@ Transactor::checkSign(PreclaimContext const& ctx)
return checkMultiSign(ctx.view, idAccount, txSigners, ctx.flags, ctx.j);
}
// if (ctx.tx.isFieldPresent(sfSponsor))
// {
// // TODO: check the sponsor signature
// }
if (ctx.tx.isFieldPresent(sfSponsor))
{
if (auto const ret = checkSponsorSign(ctx); !isTesSuccess(ret))
return ret;
}
// Check Single Sign
XRPL_ASSERT(
@@ -721,6 +722,51 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
return ret;
}
NotTEC
Transactor::checkSponsorSign(PreclaimContext const& ctx)
{
NotTEC ret = tesSUCCESS;
if (!ctx.tx.isFieldPresent(sfSponsor))
return tesSUCCESS;
auto const sponsorObj = ctx.tx.getFieldObject(sfSponsor);
auto const sponsorAcc = sponsorObj.getAccountID(sfAccount);
Blob const& pkSigner = sponsorObj.getFieldVL(sfSigningPubKey);
auto const sleAccount = ctx.view.read(keylet::account(sponsorAcc));
if (!sleAccount)
return tefBAD_AUTH;
if (pkSigner.empty())
{
STArray const& txSigners(sponsorObj.getFieldArray(sfSigners));
if (ret = checkMultiSign(
ctx.view, sponsorAcc, txSigners, ctx.flags, ctx.j);
!isTesSuccess(ret))
return ret;
}
else
{
// LCOV_EXCL_START
if (!publicKeyType(makeSlice(pkSigner)))
return tefBAD_AUTH;
// LCOV_EXCL_STOP
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
if (ret = checkSingleSign(
idSigner, sponsorAcc, sleAccount, ctx.view.rules(), ctx.j);
!isTesSuccess(ret))
{
return ret;
}
}
return ret;
}
NotTEC
Transactor::checkSingleSign(
AccountID const& idSigner,

View File

@@ -192,6 +192,9 @@ public:
static NotTEC
checkBatchSign(PreclaimContext const& ctx);
static NotTEC
checkSponsorSign(PreclaimContext const& ctx);
// Returns the fee in fee units, not scaled for load.
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);

View File

@@ -83,6 +83,17 @@ checkValidity(
? STTx::RequireFullyCanonicalSig::yes
: STTx::RequireFullyCanonicalSig::no;
if (tx.isFieldPresent(sfSponsor) && rules.enabled(featureSponsor))
{
auto const sigVerify =
tx.checkSponsorSign(requireCanonicalSig, rules);
if (!sigVerify)
{
router.setFlags(id, SF_SIGBAD);
return {Validity::SigBad, sigVerify.error()};
}
}
auto const sigVerify = tx.checkSign(requireCanonicalSig, rules);
if (!sigVerify)
{