use sfSponsorSignature

This commit is contained in:
tequ
2025-10-03 22:09:46 +09:00
parent 8d32b0f856
commit 1441abcf01
18 changed files with 255 additions and 521 deletions

View File

@@ -249,8 +249,6 @@ public:
getFieldObject(SField const& field) const;
STArray const&
getFieldArray(SField const& field) const;
STObject const&
getFieldObject(SField const& field) const;
STCurrency const&
getFieldCurrency(SField const& field) const;
STNumber const&

View File

@@ -115,12 +115,6 @@ public:
boost::container::flat_set<AccountID>
getMentionedAccounts() const;
static Blob
getSigningData(STObject const& that);
static Blob
getSponsorSigningData(STTx const& that);
uint256
getTransactionID() const;
@@ -168,11 +162,6 @@ 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();
@@ -208,23 +197,12 @@ 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*
@@ -273,46 +251,6 @@ 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,6 +61,31 @@ 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

@@ -383,7 +383,8 @@ 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, SField::sMD_Default, SField::notSigning)
UNTYPED_SFIELD(sfSponsor, OBJECT, 37)
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::sMD_Default, SField::notSigning)
// array of objects (common)
// ARRAY/1 is reserved for end of array

View File

@@ -178,6 +178,11 @@ InnerObjectFormats::InnerObjectFormats()
{
{sfAccount, soeREQUIRED},
{sfFlags, soeREQUIRED},
});
add(sfSponsorSignature.jsonName.c_str(),
sfSponsorSignature.getCode(),
{
{sfSigningPubKey, soeOPTIONAL},
{sfTxnSignature, soeOPTIONAL},
{sfSigners, soeOPTIONAL},

View File

@@ -705,13 +705,6 @@ STObject::getFieldArray(SField const& field) const
return getFieldByConstRef<STArray>(field, empty);
}
STObject const&
STObject::getFieldObject(SField const& field) const
{
static STObject const empty(field);
return getFieldByConstRef<STObject>(field, empty);
}
STCurrency const&
STObject::getFieldCurrency(SField const& field) const
{

View File

@@ -49,7 +49,6 @@
#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>
@@ -185,26 +184,15 @@ STTx::getMentionedAccounts() const
return list;
}
Blob
STTx::getSigningData(STObject const& that)
static Blob
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
{
@@ -299,15 +287,14 @@ STTx::checkSign(
if (auto const ret = checkSign(requireCanonicalSig, rules, nullptr); !ret)
return ret;
/* Placeholder for field that will be added by Lending Protocol
if (isFieldPresent(sfCounterpartySignature))
if (isFieldPresent(sfSponsorSignature))
{
auto const counterSig = getFieldObject(sfCounterpartySignature);
if (auto const ret = checkSign(requireCanonicalSig, rules, &counterSig);
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
if (auto const ret =
checkSign(requireCanonicalSig, rules, &sponsorSignatureObj);
!ret)
return Unexpected("Counterparty: " + ret.error());
return Unexpected("Sponsor: " + ret.error());
}
*/
return {};
}
@@ -347,42 +334,6 @@ 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
{
@@ -523,17 +474,6 @@ 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& sigObject,
@@ -640,32 +580,6 @@ 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,
@@ -964,90 +878,4 @@ 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,4 +61,52 @@ 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

@@ -48,6 +48,7 @@ TxFormats::TxFormats()
{sfNetworkID, soeOPTIONAL},
{sfDelegate, soeOPTIONAL},
{sfSponsor, soeOPTIONAL},
{sfSponsorSignature, soeOPTIONAL},
};
#pragma push_macro("UNWRAP")

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,6 @@
#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>
@@ -58,7 +57,7 @@ struct JTx
// Functions that sign the transaction from the Account
std::vector<std::function<void(Env&, JTx&)>> mainSigners;
// Functions that sign something else after the mainSigners, such as
// sfCounterpartySignature
// sfCounterpartySignature and sfSponsorSignature
std::vector<std::function<void(Env&, JTx&)>> postSigners;
JTx() = default;

View File

@@ -592,9 +592,6 @@ 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

@@ -70,7 +70,8 @@ msig::operator()(Env& env, JTx& jt) const
{
auto const mySigners = signers;
auto callback = [subField = subField, mySigners, &env](Env&, JTx& jtx) {
// Where to put the signature. Supports sfCounterPartySignature.
// Where to put the signature. Supports sfCounterPartySignature and
// sfSponsorSignature.
auto& sigObject = subField ? jtx[*subField] : jtx.jv;
// The signing pub key is only required at the top level.

View File

@@ -119,76 +119,6 @@ as::operator()(Env& env, JTx& jt) const
jt.jv[sfSponsor.jsonName][sfFlags.jsonName] = flags;
}
void
sig::operator()(Env& env, JTx& jt) const
{
auto const signer = signer_;
jt.jv[sfSponsor.jsonName][sfAccount.jsonName] = signer.acct.human();
jt.jv[sfSponsor.jsonName][sfSigningPubKey.jsonName] =
strHex(signer.sig.pk().slice());
jt.sponsorSigner = [signer, &env](Env&, JTx& jtx) {
std::optional<STObject> st;
try
{
Json::Value jv = jtx.jv;
st = parse(jv);
}
catch (parse_error const&)
{
env.test.log << pretty(jtx.jv) << std::endl;
Rethrow();
}
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
{
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.jsonName];
jo[jss::Account] = e.acct.human();
jo[jss::SigningPubKey] = strHex(e.sig.pk().slice());
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.jsonName] = strHex(Slice{sig.data(), sig.size()});
}
};
}
Json::Value
ledgerEntry(
jtx::Env& env,

View File

@@ -101,35 +101,6 @@ public:
operator()(jtx::Env&, jtx::JTx& jtx) const;
};
struct sig
{
private:
Reg signer_;
public:
sig(Reg signer) : signer_(std::move(signer))
{
}
void
operator()(jtx::Env&, jtx::JTx& jtx) const;
};
struct msig
{
private:
std::vector<Reg> signers;
public:
msig(std::vector<Reg> signers_) : signers(std::move(signers_))
{
sortSigners(signers);
}
void
operator()(jtx::Env&, jtx::JTx& jtx) const;
};
Json::Value
ledgerEntry(
jtx::Env& env,

View File

@@ -870,7 +870,7 @@ class AccountTx_test : public beast::unit_test::suite
// fee sponsorship
env(noop(alice),
sponsor::as(sponsor, tfSponsorFee),
sponsor::sig(sponsor));
sig(sfSponsorSignature, sponsor));
env.close();
checkTx(alice, jss::AccountSet);
checkTx(sponsor, jss::AccountSet);
@@ -893,7 +893,7 @@ class AccountTx_test : public beast::unit_test::suite
// transfer object sponsorship
env(sponsor::transfer(alice, keylet::ticket(alice, seq + 1).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
sig(sfSponsorSignature, sponsor2));
env.close();
checkTx(alice, jss::SponsorshipTransfer);
checkTx(sponsor, jss::SponsorshipTransfer);
@@ -903,7 +903,7 @@ class AccountTx_test : public beast::unit_test::suite
env(noop(alice),
ticket::use(seq + 1),
sponsor::as(sponsor, tfSponsorFee),
sponsor::sig(sponsor));
sig(sfSponsorSignature, sponsor));
env.close();
checkTx(alice, jss::AccountSet);
checkTx(sponsor, jss::AccountSet);
@@ -912,7 +912,7 @@ class AccountTx_test : public beast::unit_test::suite
// account sponsorship
env(sponsor::transfer(alice),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
sig(sfSponsorSignature, sponsor));
env.close();
checkTx(alice, jss::SponsorshipTransfer);
checkTx(sponsor, jss::SponsorshipTransfer);

View File

@@ -322,9 +322,7 @@ Transactor::checkSponsor(ReadView const& view, STTx const& tx)
auto const sponsorAcc = txSponsor.getAccountID(sfAccount);
auto const sponseeAcc = tx.getAccountID(sfAccount);
auto const hasSponsorSignature = txSponsor.isFieldPresent(sfTxnSignature) ||
!txSponsor.getFieldVL(sfSigningPubKey).empty() ||
txSponsor.isFieldPresent(sfSigners);
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
if (!hasSponsorSignature)
{
@@ -787,17 +785,20 @@ Transactor::checkSign(
return tesSUCCESS;
}
if (sigObject.isFieldPresent(sfSponsor))
if (sigObject.isFieldPresent(sfSponsorSignature))
{
if (!sigObject.isFieldPresent(sfSponsor))
return temMALFORMED;
auto const sponsorObj = sigObject.getFieldObject(sfSponsor);
auto const isCoSigned = sponsorObj.isFieldPresent(sfTxnSignature) ||
!sponsorObj.getFieldVL(sfSigningPubKey).empty() ||
sponsorObj.isFieldPresent(sfSigners);
auto const isCoSigned = sigObject.isFieldPresent(sfSponsorSignature);
if (isCoSigned)
{
auto const sponsorAcc = sponsorObj.getAccountID(sfAccount);
auto const sponsorSignature =
sigObject.getFieldObject(sfSponsorSignature);
if (auto const ret =
checkSign(view, flags, sponsorAcc, sponsorObj, j);
checkSign(view, flags, sponsorAcc, sponsorSignature, j);
!isTesSuccess(ret))
return ret;
}
@@ -1266,9 +1267,7 @@ Transactor::getFeePayer(STTx const& tx)
tx.getFieldObject(sfSponsor).isFlag(tfSponsorFee))
{
auto const sponsor = tx.getFieldObject(sfSponsor);
auto const hasSignature = sponsor.isFieldPresent(sfTxnSignature) ||
!sponsor.getFieldVL(sfSigningPubKey).empty() ||
sponsor.isFieldPresent(sfSigners);
auto const hasSignature = tx.isFieldPresent(sfSponsorSignature);
if (!hasSignature)
{

View File

@@ -86,13 +86,13 @@ checkValidity(
if (tx.isFieldPresent(sfSponsor) && rules.enabled(featureSponsor))
{
auto const sponsorObj = tx.getFieldObject(sfSponsor);
auto const isCoSigned = sponsorObj.isFieldPresent(sfTxnSignature) ||
!sponsorObj.getFieldVL(sfSigningPubKey).empty() ||
sponsorObj.isFieldPresent(sfSigners);
auto const isCoSigned = tx.isFieldPresent(sfSponsorSignature);
if (isCoSigned)
{
auto const sigVerify =
tx.checkSponsorSign(requireCanonicalSig, rules);
auto const sponsorSignatureObj =
tx.getFieldObject(sfSponsorSignature);
auto const sigVerify = tx.checkSign(
requireCanonicalSig, rules, &sponsorSignatureObj);
if (!sigVerify)
{
router.setFlags(id, SF_SIGBAD);