mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 01:06:48 +00:00
Sponsor signing
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
35
include/xrpl/protocol/Sponsor.h
Normal file
35
include/xrpl/protocol/Sponsor.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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()});
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user