mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-05 11:45:51 +00:00
refactor: Add support for extra transaction signature validation (#5851)
- Restructures `STTx` signature checking code to be able to handle
a `sigObject`, which may be the full transaction, or may be an object
field containing a separate signature. Either way, the `sigObject` can
be a single- or multi-sign signature.
- This is distinct from 550f90a75e (#5594), which changed the check in
Transactor, which validates whether a given account is allowed to sign
for the given transaction. This cryptographically checks the signature
validity.
This commit is contained in:
@@ -244,6 +244,9 @@ public:
|
||||
getFieldPathSet(SField const& field) const;
|
||||
STVector256 const&
|
||||
getFieldV256(SField const& field) const;
|
||||
// If not found, returns an object constructed with the given field
|
||||
STObject
|
||||
getFieldObject(SField const& field) const;
|
||||
STArray const&
|
||||
getFieldArray(SField const& field) const;
|
||||
STCurrency const&
|
||||
@@ -390,6 +393,8 @@ public:
|
||||
setFieldV256(SField const& field, STVector256 const& v);
|
||||
void
|
||||
setFieldArray(SField const& field, STArray const& v);
|
||||
void
|
||||
setFieldObject(SField const& field, STObject const& v);
|
||||
|
||||
template <class Tag>
|
||||
void
|
||||
|
||||
@@ -87,8 +87,14 @@ public:
|
||||
getFullText() const override;
|
||||
|
||||
// Outer transaction functions / signature functions.
|
||||
static Blob
|
||||
getSignature(STObject const& sigObject);
|
||||
|
||||
Blob
|
||||
getSignature() const;
|
||||
getSignature() const
|
||||
{
|
||||
return getSignature(*this);
|
||||
}
|
||||
|
||||
uint256
|
||||
getSigningHash() const;
|
||||
@@ -119,13 +125,20 @@ public:
|
||||
getJson(JsonOptions options, bool binary) const;
|
||||
|
||||
void
|
||||
sign(PublicKey const& publicKey, SecretKey const& secretKey);
|
||||
sign(
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
std::optional<std::reference_wrapper<SField const>> signatureTarget =
|
||||
{});
|
||||
|
||||
/** Check the signature.
|
||||
@return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
enum class RequireFullyCanonicalSig : bool { no, yes };
|
||||
|
||||
/** Check the signature.
|
||||
@param requireCanonicalSig If `true`, check that the signature is fully
|
||||
canonical. If `false`, only check that the signature is valid.
|
||||
@param rules The current ledger rules.
|
||||
@return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
Expected<void, std::string>
|
||||
checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules)
|
||||
const;
|
||||
@@ -150,17 +163,34 @@ public:
|
||||
char status,
|
||||
std::string const& escapedMetaData) const;
|
||||
|
||||
std::vector<uint256>
|
||||
std::vector<uint256> const&
|
||||
getBatchTransactionIDs() const;
|
||||
|
||||
private:
|
||||
/** Check the signature.
|
||||
@param requireCanonicalSig If `true`, check that the signature is fully
|
||||
canonical. If `false`, only check that the signature is valid.
|
||||
@param rules The current ledger rules.
|
||||
@param sigObject Reference to object that contains the signature fields.
|
||||
Will be *this more often than not.
|
||||
@return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
Expected<void, std::string>
|
||||
checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const;
|
||||
checkSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules,
|
||||
STObject const& sigObject) const;
|
||||
|
||||
Expected<void, std::string>
|
||||
checkSingleSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
STObject const& sigObject) const;
|
||||
|
||||
Expected<void, std::string>
|
||||
checkMultiSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules) const;
|
||||
Rules const& rules,
|
||||
STObject const& sigObject) const;
|
||||
|
||||
Expected<void, std::string>
|
||||
checkBatchSingleSign(
|
||||
@@ -179,7 +209,7 @@ private:
|
||||
move(std::size_t n, void* buf) override;
|
||||
|
||||
friend class detail::STVar;
|
||||
mutable std::vector<uint256> batch_txn_ids_;
|
||||
mutable std::vector<uint256> batchTxnIds_;
|
||||
};
|
||||
|
||||
bool
|
||||
|
||||
@@ -569,6 +569,7 @@ JSS(settle_delay); // out: AccountChannels
|
||||
JSS(severity); // in: LogLevel
|
||||
JSS(shares); // out: VaultInfo
|
||||
JSS(signature); // out: NetworkOPs, ChannelAuthorize
|
||||
JSS(signature_target); // in: TransactionSign
|
||||
JSS(signature_verified); // out: ChannelVerify
|
||||
JSS(signing_key); // out: NetworkOPs
|
||||
JSS(signing_keys); // out: ValidatorList
|
||||
|
||||
@@ -688,6 +688,16 @@ STObject::getFieldV256(SField const& field) const
|
||||
return getFieldByConstRef<STVector256>(field, empty);
|
||||
}
|
||||
|
||||
STObject
|
||||
STObject::getFieldObject(SField const& field) const
|
||||
{
|
||||
STObject const empty{field};
|
||||
auto ret = getFieldByConstRef<STObject>(field, empty);
|
||||
if (ret != empty)
|
||||
ret.applyTemplateFromSField(field);
|
||||
return ret;
|
||||
}
|
||||
|
||||
STArray const&
|
||||
STObject::getFieldArray(SField const& field) const
|
||||
{
|
||||
@@ -833,6 +843,12 @@ STObject::setFieldArray(SField const& field, STArray const& v)
|
||||
setFieldUsingAssignment(field, v);
|
||||
}
|
||||
|
||||
void
|
||||
STObject::setFieldObject(SField const& field, STObject const& v)
|
||||
{
|
||||
setFieldUsingAssignment(field, v);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
STObject::getJson(JsonOptions options) const
|
||||
{
|
||||
|
||||
@@ -200,11 +200,11 @@ STTx::getSigningHash() const
|
||||
}
|
||||
|
||||
Blob
|
||||
STTx::getSignature() const
|
||||
STTx::getSignature(STObject const& sigObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
return getFieldVL(sfTxnSignature);
|
||||
return sigObject.getFieldVL(sfTxnSignature);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
@@ -234,35 +234,68 @@ STTx::getSeqValue() const
|
||||
}
|
||||
|
||||
void
|
||||
STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
STTx::sign(
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
std::optional<std::reference_wrapper<SField const>> signatureTarget)
|
||||
{
|
||||
auto const data = getSigningData(*this);
|
||||
|
||||
auto const sig = ripple::sign(publicKey, secretKey, makeSlice(data));
|
||||
|
||||
setFieldVL(sfTxnSignature, sig);
|
||||
if (signatureTarget)
|
||||
{
|
||||
auto& target = peekFieldObject(*signatureTarget);
|
||||
target.setFieldVL(sfTxnSignature, sig);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFieldVL(sfTxnSignature, sig);
|
||||
}
|
||||
tid_ = getHash(HashPrefix::transactionID);
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
STTx::checkSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules,
|
||||
STObject const& sigObject) const
|
||||
{
|
||||
try
|
||||
{
|
||||
// Determine whether we're single- or multi-signing by looking
|
||||
// at the SigningPubKey. If it's empty we must be
|
||||
// multi-signing. Otherwise we're single-signing.
|
||||
|
||||
Blob const& signingPubKey = sigObject.getFieldVL(sfSigningPubKey);
|
||||
return signingPubKey.empty()
|
||||
? checkMultiSign(requireCanonicalSig, rules, sigObject)
|
||||
: checkSingleSign(requireCanonicalSig, sigObject);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
return Unexpected("Internal signature check failure.");
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
STTx::checkSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules) const
|
||||
{
|
||||
try
|
||||
if (auto const ret = checkSign(requireCanonicalSig, rules, *this); !ret)
|
||||
return ret;
|
||||
|
||||
/* Placeholder for field that will be added by Lending Protocol
|
||||
if (isFieldPresent(sfCounterpartySignature))
|
||||
{
|
||||
// Determine whether we're single- or multi-signing by looking
|
||||
// at the SigningPubKey. If it's empty we must be
|
||||
// multi-signing. Otherwise we're single-signing.
|
||||
Blob const& signingPubKey = getFieldVL(sfSigningPubKey);
|
||||
return signingPubKey.empty()
|
||||
? checkMultiSign(requireCanonicalSig, rules)
|
||||
: checkSingleSign(requireCanonicalSig);
|
||||
auto const counterSig = getFieldObject(sfCounterpartySignature);
|
||||
if (auto const ret = checkSign(requireCanonicalSig, rules, counterSig);
|
||||
!ret)
|
||||
return Unexpected("Counterparty: " + ret.error());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
return Unexpected("Internal signature check failure.");
|
||||
*/
|
||||
return {};
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
@@ -382,23 +415,23 @@ STTx::getMetaSQL(
|
||||
|
||||
static Expected<void, std::string>
|
||||
singleSignHelper(
|
||||
STObject const& signer,
|
||||
STObject const& sigObject,
|
||||
Slice const& data,
|
||||
bool const fullyCanonical)
|
||||
{
|
||||
// We don't allow both a non-empty sfSigningPubKey and an sfSigners.
|
||||
// That would allow the transaction to be signed two ways. So if both
|
||||
// fields are present the signature is invalid.
|
||||
if (signer.isFieldPresent(sfSigners))
|
||||
if (sigObject.isFieldPresent(sfSigners))
|
||||
return Unexpected("Cannot both single- and multi-sign.");
|
||||
|
||||
bool validSig = false;
|
||||
try
|
||||
{
|
||||
auto const spk = signer.getFieldVL(sfSigningPubKey);
|
||||
auto const spk = sigObject.getFieldVL(sfSigningPubKey);
|
||||
if (publicKeyType(makeSlice(spk)))
|
||||
{
|
||||
Blob const signature = signer.getFieldVL(sfTxnSignature);
|
||||
Blob const signature = sigObject.getFieldVL(sfTxnSignature);
|
||||
validSig = verify(
|
||||
PublicKey(makeSlice(spk)),
|
||||
data,
|
||||
@@ -418,12 +451,14 @@ singleSignHelper(
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
|
||||
STTx::checkSingleSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
STObject const& sigObject) const
|
||||
{
|
||||
auto const data = getSigningData(*this);
|
||||
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
|
||||
(requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes);
|
||||
return singleSignHelper(*this, makeSlice(data), fullyCanonical);
|
||||
return singleSignHelper(sigObject, makeSlice(data), fullyCanonical);
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
@@ -440,31 +475,29 @@ STTx::checkBatchSingleSign(
|
||||
|
||||
Expected<void, std::string>
|
||||
multiSignHelper(
|
||||
STObject const& signerObj,
|
||||
STObject const& sigObject,
|
||||
std::optional<AccountID> txnAccountID,
|
||||
bool const fullyCanonical,
|
||||
std::function<Serializer(AccountID const&)> makeMsg,
|
||||
Rules const& rules)
|
||||
{
|
||||
// Make sure the MultiSigners are present. Otherwise they are not
|
||||
// attempting multi-signing and we just have a bad SigningPubKey.
|
||||
if (!signerObj.isFieldPresent(sfSigners))
|
||||
if (!sigObject.isFieldPresent(sfSigners))
|
||||
return Unexpected("Empty SigningPubKey.");
|
||||
|
||||
// We don't allow both an sfSigners and an sfTxnSignature. Both fields
|
||||
// being present would indicate that the transaction is signed both ways.
|
||||
if (signerObj.isFieldPresent(sfTxnSignature))
|
||||
if (sigObject.isFieldPresent(sfTxnSignature))
|
||||
return Unexpected("Cannot both single- and multi-sign.");
|
||||
|
||||
STArray const& signers{signerObj.getFieldArray(sfSigners)};
|
||||
STArray const& signers{sigObject.getFieldArray(sfSigners)};
|
||||
|
||||
// There are well known bounds that the number of signers must be within.
|
||||
if (signers.size() < STTx::minMultiSigners ||
|
||||
signers.size() > STTx::maxMultiSigners(&rules))
|
||||
return Unexpected("Invalid Signers array size.");
|
||||
|
||||
// We also use the sfAccount field inside the loop. Get it once.
|
||||
auto const txnAccountID = signerObj.getAccountID(sfAccount);
|
||||
|
||||
// Signers must be in sorted order by AccountID.
|
||||
AccountID lastAccountID(beast::zero);
|
||||
|
||||
@@ -472,8 +505,10 @@ multiSignHelper(
|
||||
{
|
||||
auto const accountID = signer.getAccountID(sfAccount);
|
||||
|
||||
// The account owner may not multisign for themselves.
|
||||
if (accountID == txnAccountID)
|
||||
// The account owner may not usually multisign for themselves.
|
||||
// If they can, txnAccountID will be unseated, which is not equal to any
|
||||
// value.
|
||||
if (txnAccountID == accountID)
|
||||
return Unexpected("Invalid multisigner.");
|
||||
|
||||
// No duplicate signers allowed.
|
||||
@@ -489,6 +524,7 @@ multiSignHelper(
|
||||
|
||||
// Verify the signature.
|
||||
bool validSig = false;
|
||||
std::optional<std::string> errorWhat;
|
||||
try
|
||||
{
|
||||
auto spk = signer.getFieldVL(sfSigningPubKey);
|
||||
@@ -502,15 +538,16 @@ multiSignHelper(
|
||||
fullyCanonical);
|
||||
}
|
||||
}
|
||||
catch (std::exception const&)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
// We assume any problem lies with the signature.
|
||||
validSig = false;
|
||||
errorWhat = e.what();
|
||||
}
|
||||
if (!validSig)
|
||||
return Unexpected(
|
||||
std::string("Invalid signature on account ") +
|
||||
toBase58(accountID) + ".");
|
||||
toBase58(accountID) + errorWhat.value_or("") + ".");
|
||||
}
|
||||
// All signatures verified.
|
||||
return {};
|
||||
@@ -532,8 +569,9 @@ STTx::checkBatchMultiSign(
|
||||
serializeBatch(dataStart, getFlags(), getBatchTransactionIDs());
|
||||
return multiSignHelper(
|
||||
batchSigner,
|
||||
std::nullopt,
|
||||
fullyCanonical,
|
||||
[&dataStart](AccountID const& accountID) mutable -> Serializer {
|
||||
[&dataStart](AccountID const& accountID) -> Serializer {
|
||||
Serializer s = dataStart;
|
||||
finishMultiSigningData(accountID, s);
|
||||
return s;
|
||||
@@ -544,19 +582,27 @@ STTx::checkBatchMultiSign(
|
||||
Expected<void, std::string>
|
||||
STTx::checkMultiSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules) const
|
||||
Rules const& rules,
|
||||
STObject const& sigObject) const
|
||||
{
|
||||
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
|
||||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
|
||||
|
||||
// Used inside the loop in multiSignHelper to enforce that
|
||||
// the account owner may not multisign for themselves.
|
||||
auto const txnAccountID = &sigObject != this
|
||||
? std::nullopt
|
||||
: std::optional<AccountID>(getAccountID(sfAccount));
|
||||
|
||||
// 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.
|
||||
Serializer dataStart = startMultiSigningData(*this);
|
||||
return multiSignHelper(
|
||||
*this,
|
||||
sigObject,
|
||||
txnAccountID,
|
||||
fullyCanonical,
|
||||
[&dataStart](AccountID const& accountID) mutable -> Serializer {
|
||||
[&dataStart](AccountID const& accountID) -> Serializer {
|
||||
Serializer s = dataStart;
|
||||
finishMultiSigningData(accountID, s);
|
||||
return s;
|
||||
@@ -569,7 +615,7 @@ STTx::checkMultiSign(
|
||||
*
|
||||
* This function returns a vector of transaction IDs by extracting them from
|
||||
* the field array `sfRawTransactions` within the STTx. If the batch
|
||||
* transaction IDs have already been computed and cached in `batch_txn_ids_`,
|
||||
* transaction IDs have already been computed and cached in `batchTxnIds_`,
|
||||
* it returns the cached vector. Otherwise, it computes the transaction IDs,
|
||||
* caches them, and then returns the vector.
|
||||
*
|
||||
@@ -579,7 +625,7 @@ STTx::checkMultiSign(
|
||||
* empty and that the size of the computed batch transaction IDs matches the
|
||||
* size of the `sfRawTransactions` field array.
|
||||
*/
|
||||
std::vector<uint256>
|
||||
std::vector<uint256> const&
|
||||
STTx::getBatchTransactionIDs() const
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -588,16 +634,20 @@ STTx::getBatchTransactionIDs() const
|
||||
XRPL_ASSERT(
|
||||
getFieldArray(sfRawTransactions).size() != 0,
|
||||
"STTx::getBatchTransactionIDs : empty raw transactions");
|
||||
if (batch_txn_ids_.size() != 0)
|
||||
return batch_txn_ids_;
|
||||
|
||||
for (STObject const& rb : getFieldArray(sfRawTransactions))
|
||||
batch_txn_ids_.push_back(rb.getHash(HashPrefix::transactionID));
|
||||
// The list of inner ids is built once, then reused on subsequent calls.
|
||||
// After the list is built, it must always have the same size as the array
|
||||
// `sfRawTransactions`. The assert below verifies that.
|
||||
if (batchTxnIds_.size() == 0)
|
||||
{
|
||||
for (STObject const& rb : getFieldArray(sfRawTransactions))
|
||||
batchTxnIds_.push_back(rb.getHash(HashPrefix::transactionID));
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
batch_txn_ids_.size() == getFieldArray(sfRawTransactions).size(),
|
||||
batchTxnIds_.size() == getFieldArray(sfRawTransactions).size(),
|
||||
"STTx::getBatchTransactionIDs : batch transaction IDs size mismatch");
|
||||
return batch_txn_ids_;
|
||||
return batchTxnIds_;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -54,7 +54,11 @@ struct JTx
|
||||
bool fill_sig = true;
|
||||
bool fill_netid = true;
|
||||
std::shared_ptr<STTx const> stx;
|
||||
std::function<void(Env&, JTx&)> signer;
|
||||
// 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
|
||||
std::vector<std::function<void(Env&, JTx&)>> postSigners;
|
||||
|
||||
JTx() = default;
|
||||
JTx(JTx const&) = default;
|
||||
|
||||
@@ -618,7 +618,7 @@ create(
|
||||
|
||||
} // namespace check
|
||||
|
||||
static constexpr FeeLevel64 baseFeeLevel{256};
|
||||
static constexpr FeeLevel64 baseFeeLevel{TxQ::baseLevel};
|
||||
static constexpr FeeLevel64 minEscalationFeeLevel = baseFeeLevel * 500;
|
||||
|
||||
template <class Suite>
|
||||
|
||||
@@ -213,14 +213,16 @@ public:
|
||||
|
||||
template <std::integral T>
|
||||
PrettyAmount
|
||||
operator()(T v) const
|
||||
operator()(T v, Number::rounding_mode rounding = Number::getround()) const
|
||||
{
|
||||
return operator()(Number(v));
|
||||
return operator()(Number(v), rounding);
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
operator()(Number v) const
|
||||
operator()(Number v, Number::rounding_mode rounding = Number::getround())
|
||||
const
|
||||
{
|
||||
NumberRoundModeGuard mg(rounding);
|
||||
STAmount amount{asset_, v * scale_};
|
||||
return {amount, ""};
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/net/HTTPClient.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
@@ -531,8 +532,22 @@ void
|
||||
Env::autofill_sig(JTx& jt)
|
||||
{
|
||||
auto& jv = jt.jv;
|
||||
if (jt.signer)
|
||||
return jt.signer(*this, jt);
|
||||
|
||||
scope_success success([&]() {
|
||||
// Call all the post-signers after the main signers or autofill are done
|
||||
for (auto const& signer : jt.postSigners)
|
||||
signer(*this, jt);
|
||||
});
|
||||
|
||||
// Call all the main signers
|
||||
if (!jt.mainSigners.empty())
|
||||
{
|
||||
for (auto const& signer : jt.mainSigners)
|
||||
signer(*this, jt);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the sig is still needed, get it here.
|
||||
if (!jt.fill_sig)
|
||||
return;
|
||||
auto const account = jv.isMember(sfDelegate.jsonName)
|
||||
|
||||
@@ -507,7 +507,7 @@ MPTTester::getFlags(std::optional<Account> const& holder) const
|
||||
}
|
||||
|
||||
MPT
|
||||
MPTTester::operator[](std::string const& name)
|
||||
MPTTester::operator[](std::string const& name) const
|
||||
{
|
||||
return MPT(name, issuanceID());
|
||||
}
|
||||
|
||||
@@ -69,8 +69,15 @@ void
|
||||
msig::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
auto const mySigners = signers;
|
||||
jt.signer = [mySigners, &env](Env&, JTx& jtx) {
|
||||
jtx[sfSigningPubKey.getJsonName()] = "";
|
||||
auto callback = [subField = subField, mySigners, &env](Env&, JTx& jtx) {
|
||||
// Where to put the signature. Supports sfCounterPartySignature.
|
||||
auto& sigObject = subField ? jtx[*subField] : jtx.jv;
|
||||
|
||||
// The signing pub key is only required at the top level.
|
||||
if (!subField)
|
||||
sigObject[sfSigningPubKey] = "";
|
||||
else if (sigObject.isNull())
|
||||
sigObject = Json::Value(Json::objectValue);
|
||||
std::optional<STObject> st;
|
||||
try
|
||||
{
|
||||
@@ -81,7 +88,7 @@ msig::operator()(Env& env, JTx& jt) const
|
||||
env.test.log << pretty(jtx.jv) << std::endl;
|
||||
Rethrow();
|
||||
}
|
||||
auto& js = jtx[sfSigners.getJsonName()];
|
||||
auto& js = sigObject[sfSigners];
|
||||
for (std::size_t i = 0; i < mySigners.size(); ++i)
|
||||
{
|
||||
auto const& e = mySigners[i];
|
||||
@@ -96,6 +103,10 @@ msig::operator()(Env& env, JTx& jt) const
|
||||
strHex(Slice{sig.data(), sig.size()});
|
||||
}
|
||||
};
|
||||
if (!subField)
|
||||
jt.mainSigners.emplace_back(callback);
|
||||
else
|
||||
jt.postSigners.emplace_back(callback);
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
@@ -29,12 +29,22 @@ sig::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
if (!manual_)
|
||||
return;
|
||||
jt.fill_sig = false;
|
||||
if (!subField_)
|
||||
jt.fill_sig = false;
|
||||
if (account_)
|
||||
{
|
||||
// VFALCO Inefficient pre-C++14
|
||||
auto const account = *account_;
|
||||
jt.signer = [account](Env&, JTx& jtx) { jtx::sign(jtx.jv, account); };
|
||||
auto callback = [subField = subField_, account](Env&, JTx& jtx) {
|
||||
// Where to put the signature. Supports sfCounterPartySignature.
|
||||
auto& sigObject = subField ? jtx[*subField] : jtx.jv;
|
||||
|
||||
jtx::sign(jtx.jv, account, sigObject);
|
||||
};
|
||||
if (!subField_)
|
||||
jt.mainSigners.emplace_back(callback);
|
||||
else
|
||||
jt.postSigners.emplace_back(callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,14 +44,20 @@ parse(Json::Value const& jv)
|
||||
}
|
||||
|
||||
void
|
||||
sign(Json::Value& jv, Account const& account)
|
||||
sign(Json::Value& jv, Account const& account, Json::Value& sigObject)
|
||||
{
|
||||
jv[jss::SigningPubKey] = strHex(account.pk().slice());
|
||||
sigObject[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());
|
||||
jv[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
|
||||
sigObject[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
|
||||
}
|
||||
|
||||
void
|
||||
sign(Json::Value& jv, Account const& account)
|
||||
{
|
||||
sign(jv, account, jv);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -235,7 +235,7 @@ public:
|
||||
getBalance(Account const& account) const;
|
||||
|
||||
MPT
|
||||
operator[](std::string const& name);
|
||||
operator[](std::string const& name) const;
|
||||
|
||||
private:
|
||||
using SLEP = std::shared_ptr<SLE const>;
|
||||
|
||||
@@ -67,18 +67,63 @@ class msig
|
||||
{
|
||||
public:
|
||||
std::vector<Reg> signers;
|
||||
/** Alternative transaction object field in which to place the signer list.
|
||||
*
|
||||
* subField is only supported if an account_ is provided as well.
|
||||
*/
|
||||
SField const* const subField = nullptr;
|
||||
/// Used solely as a convenience placeholder for ctors that do _not_ specify
|
||||
/// a subfield.
|
||||
static constexpr SField* const topLevel = nullptr;
|
||||
|
||||
msig(std::vector<Reg> signers_) : signers(std::move(signers_))
|
||||
msig(SField const* subField_, std::vector<Reg> signers_)
|
||||
: signers(std::move(signers_)), subField(subField_)
|
||||
{
|
||||
sortSigners(signers);
|
||||
}
|
||||
|
||||
msig(SField const& subField_, std::vector<Reg> signers_)
|
||||
: msig{&subField_, signers_}
|
||||
{
|
||||
}
|
||||
|
||||
msig(std::vector<Reg> signers_) : msig(topLevel, signers_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class AccountType, class... Accounts>
|
||||
requires std::convertible_to<AccountType, Reg>
|
||||
explicit msig(AccountType&& a0, Accounts&&... aN)
|
||||
: signers{std::forward<AccountType>(a0), std::forward<Accounts>(aN)...}
|
||||
explicit msig(SField const* subField_, AccountType&& a0, Accounts&&... aN)
|
||||
: msig{
|
||||
subField_,
|
||||
std::vector<Reg>{
|
||||
std::forward<AccountType>(a0),
|
||||
std::forward<Accounts>(aN)...}}
|
||||
{
|
||||
}
|
||||
|
||||
template <class AccountType, class... Accounts>
|
||||
requires std::convertible_to<AccountType, Reg>
|
||||
explicit msig(SField const& subField_, AccountType&& a0, Accounts&&... aN)
|
||||
: msig{
|
||||
&subField_,
|
||||
std::vector<Reg>{
|
||||
std::forward<AccountType>(a0),
|
||||
std::forward<Accounts>(aN)...}}
|
||||
{
|
||||
}
|
||||
|
||||
template <class AccountType, class... Accounts>
|
||||
requires(
|
||||
std::convertible_to<AccountType, Reg> &&
|
||||
!std::is_same_v<AccountType, SField*>)
|
||||
explicit msig(AccountType&& a0, Accounts&&... aN)
|
||||
: msig{
|
||||
topLevel,
|
||||
std::vector<Reg>{
|
||||
std::forward<AccountType>(a0),
|
||||
std::forward<Accounts>(aN)...}}
|
||||
{
|
||||
sortSigners(signers);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -35,7 +35,20 @@ class sig
|
||||
{
|
||||
private:
|
||||
bool manual_ = true;
|
||||
/** Alternative transaction object field in which to place the signature.
|
||||
*
|
||||
* subField is only supported if an account_ is provided as well.
|
||||
*/
|
||||
SField const* const subField_ = nullptr;
|
||||
/** Account that will generate the signature.
|
||||
*
|
||||
* If not provided, no signature will be added by this helper. See also
|
||||
* Env::autofill_sig.
|
||||
*/
|
||||
std::optional<Account> account_;
|
||||
/// Used solely as a convenience placeholder for ctors that do _not_ specify
|
||||
/// a subfield.
|
||||
static constexpr SField* const topLevel = nullptr;
|
||||
|
||||
public:
|
||||
explicit sig(autofill_t) : manual_(false)
|
||||
@@ -46,7 +59,17 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
explicit sig(Account const& account) : account_(account)
|
||||
explicit sig(SField const* subField, Account const& account)
|
||||
: subField_(subField), account_(account)
|
||||
{
|
||||
}
|
||||
|
||||
explicit sig(SField const& subField, Account const& account)
|
||||
: sig(&subField, account)
|
||||
{
|
||||
}
|
||||
|
||||
explicit sig(Account const& account) : sig(topLevel, account)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@ struct parse_error : std::logic_error
|
||||
STObject
|
||||
parse(Json::Value const& jv);
|
||||
|
||||
/** Sign automatically into a specific Json field of the jv object.
|
||||
@note This only works on accounts with multi-signing off.
|
||||
*/
|
||||
void
|
||||
sign(Json::Value& jv, Account const& account, Json::Value& sigObject);
|
||||
|
||||
/** Sign automatically.
|
||||
@note This only works on accounts with multi-signing off.
|
||||
*/
|
||||
|
||||
@@ -4643,10 +4643,34 @@ static RPCCallTestData const rpcCallTestArray[] = {
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"sign: too many arguments.",
|
||||
{"sign: offline flag with signature_target.",
|
||||
__LINE__,
|
||||
{"sign", "my_secret", R"({"json_argument":true})", "offline", "extra"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "sign",
|
||||
"params" : [
|
||||
{
|
||||
"api_version" : %API_VER%,
|
||||
"offline" : true,
|
||||
"secret" : "my_secret",
|
||||
"signature_target" : "extra",
|
||||
"tx_json" :
|
||||
{
|
||||
"json_argument" : true
|
||||
}
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"sign: too many arguments.",
|
||||
__LINE__,
|
||||
{"sign",
|
||||
"my_secret",
|
||||
R"({"json_argument":true})",
|
||||
"offline",
|
||||
"CounterpartySignature",
|
||||
"extra"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "sign",
|
||||
"params" : [
|
||||
@@ -4675,20 +4699,24 @@ static RPCCallTestData const rpcCallTestArray[] = {
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"sign: invalid final argument.",
|
||||
{"sign: misspelled offline flag interpreted as signature_target.",
|
||||
__LINE__,
|
||||
{"sign", "my_secret", R"({"json_argument":true})", "offlin"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "sign",
|
||||
"params" : [
|
||||
{
|
||||
"error" : "invalidParams",
|
||||
"error_code" : 31,
|
||||
"error_message" : "Invalid parameters."
|
||||
}
|
||||
]
|
||||
})"},
|
||||
"method" : "sign",
|
||||
"params" : [
|
||||
{
|
||||
"api_version" : %API_VER%,
|
||||
"secret" : "my_secret",
|
||||
"signature_target" : "offlin",
|
||||
"tx_json" :
|
||||
{
|
||||
"json_argument" : true
|
||||
}
|
||||
}
|
||||
]
|
||||
})"},
|
||||
|
||||
// sign_for
|
||||
// --------------------------------------------------------------------
|
||||
@@ -4880,10 +4908,34 @@ static RPCCallTestData const rpcCallTestArray[] = {
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"submit: too many arguments.",
|
||||
{"submit: offline flag with signature_target.",
|
||||
__LINE__,
|
||||
{"submit", "my_secret", R"({"json_argument":true})", "offline", "extra"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "submit",
|
||||
"params" : [
|
||||
{
|
||||
"api_version" : %API_VER%,
|
||||
"offline" : true,
|
||||
"secret" : "my_secret",
|
||||
"signature_target" : "extra",
|
||||
"tx_json" :
|
||||
{
|
||||
"json_argument" : true
|
||||
}
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"submit: too many arguments.",
|
||||
__LINE__,
|
||||
{"submit",
|
||||
"my_secret",
|
||||
R"({"json_argument":true})",
|
||||
"offline",
|
||||
"CounterpartySignature",
|
||||
"extra"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "submit",
|
||||
"params" : [
|
||||
@@ -4912,19 +4964,23 @@ static RPCCallTestData const rpcCallTestArray[] = {
|
||||
}
|
||||
]
|
||||
})"},
|
||||
{"submit: last argument not \"offline\".",
|
||||
{"submit: misspelled offline flag interpreted as signature_target.",
|
||||
__LINE__,
|
||||
{"submit", "my_secret", R"({"json_argument":true})", "offlne"},
|
||||
RPCCallTestData::no_exception,
|
||||
R"({
|
||||
"method" : "submit",
|
||||
"params" : [
|
||||
{
|
||||
"error" : "invalidParams",
|
||||
"error_code" : 31,
|
||||
"error_message" : "Invalid parameters."
|
||||
}
|
||||
]
|
||||
"method" : "submit",
|
||||
"params" : [
|
||||
{
|
||||
"api_version" : %API_VER%,
|
||||
"secret" : "my_secret",
|
||||
"signature_target" : "offlne",
|
||||
"tx_json" :
|
||||
{
|
||||
"json_argument" : true
|
||||
}
|
||||
}
|
||||
]
|
||||
})"},
|
||||
|
||||
// submit_multisigned
|
||||
|
||||
@@ -173,7 +173,7 @@ printHelp(po::options_description const& desc)
|
||||
" server_state [counters]\n"
|
||||
" sign <private_key> <tx_json> [offline]\n"
|
||||
" sign_for <signer_address> <signer_private_key> <tx_json> "
|
||||
"[offline]\n"
|
||||
"[offline] [<signature_field>]\n"
|
||||
" stop\n"
|
||||
" simulate [<tx_blob>|<tx_json>] [<binary>]\n"
|
||||
" submit <tx_blob>|[<private_key> <tx_json>]\n"
|
||||
|
||||
@@ -237,6 +237,39 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
std::unordered_set<uint256> uniqueHashes;
|
||||
std::unordered_map<AccountID, std::unordered_set<std::uint32_t>>
|
||||
accountSeqTicket;
|
||||
auto checkSignatureFields = [&parentBatchId, &j = ctx.j](
|
||||
STObject const& sig,
|
||||
uint256 const& hash,
|
||||
char const* label = "") -> NotTEC {
|
||||
if (sig.isFieldPresent(sfTxnSignature))
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn " << label << "cannot include TxnSignature. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (sig.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn " << label << " cannot include Signers. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (!sig.getFieldVL(sfSigningPubKey).empty())
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn " << label << " SigningPubKey must be empty. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_REGKEY;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
for (STObject rb : rawTxns)
|
||||
{
|
||||
STTx const stx = STTx{std::move(rb)};
|
||||
@@ -266,29 +299,23 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
if (stx.isFieldPresent(sfTxnSignature))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn cannot include TxnSignature. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
if (auto const ret = checkSignatureFields(stx, hash))
|
||||
return ret;
|
||||
|
||||
if (stx.isFieldPresent(sfSigners))
|
||||
/* Placeholder for field that will be added by Lending Protocol
|
||||
// Note that the CounterpartySignature is optional, and should not be
|
||||
// included, but if it is, ensure it doesn't contain a signature.
|
||||
if (stx.isFieldPresent(sfCounterpartySignature))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn cannot include Signers. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (!stx.getSigningPubKey().empty())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn SigningPubKey must be empty. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_REGKEY;
|
||||
auto const counterpartySignature =
|
||||
stx.getFieldObject(sfCounterpartySignature);
|
||||
if (auto const ret = checkSignatureFields(
|
||||
counterpartySignature, hash, "counterparty signature "))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
auto const innerAccount = stx.getAccountID(sfAccount);
|
||||
if (auto const preflightResult = ripple::preflight(
|
||||
@@ -385,6 +412,13 @@ Batch::preflightSigValidated(PreflightContext const& ctx)
|
||||
// inner account to the required signers set.
|
||||
if (innerAccount != outerAccount)
|
||||
requiredSigners.insert(innerAccount);
|
||||
/* Placeholder for field that will be added by Lending Protocol
|
||||
// Some transactions have a Counterparty, who must also sign the
|
||||
// transaction if they are not the outer account
|
||||
if (auto const counterparty = rb.at(~sfCounterparty);
|
||||
counterparty && counterparty != outerAccount)
|
||||
requiredSigners.insert(*counterparty);
|
||||
*/
|
||||
}
|
||||
|
||||
// Validation Batch Signers
|
||||
|
||||
@@ -965,7 +965,16 @@ private:
|
||||
Json::Value txJSON;
|
||||
Json::Reader reader;
|
||||
bool const bOffline =
|
||||
3 == jvParams.size() && jvParams[2u].asString() == "offline";
|
||||
jvParams.size() >= 3 && jvParams[2u].asString() == "offline";
|
||||
std::optional<std::string> const field =
|
||||
[&jvParams, bOffline]() -> std::optional<std::string> {
|
||||
if (jvParams.size() < 3)
|
||||
return std::nullopt;
|
||||
if (jvParams.size() < 4 && bOffline)
|
||||
return std::nullopt;
|
||||
Json::UInt index = bOffline ? 3u : 2u;
|
||||
return jvParams[index].asString();
|
||||
}();
|
||||
|
||||
if (1 == jvParams.size())
|
||||
{
|
||||
@@ -978,7 +987,7 @@ private:
|
||||
return jvRequest;
|
||||
}
|
||||
else if (
|
||||
(2 == jvParams.size() || bOffline) &&
|
||||
(jvParams.size() >= 2 || bOffline) &&
|
||||
reader.parse(jvParams[1u].asString(), txJSON))
|
||||
{
|
||||
// Signing or submitting tx_json.
|
||||
@@ -990,6 +999,9 @@ private:
|
||||
if (bOffline)
|
||||
jvRequest[jss::offline] = true;
|
||||
|
||||
if (field)
|
||||
jvRequest[jss::signature_target] = *field;
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
@@ -1270,11 +1282,11 @@ public:
|
||||
{"server_definitions", &RPCParser::parseServerDefinitions, 0, 1},
|
||||
{"server_info", &RPCParser::parseServerInfo, 0, 1},
|
||||
{"server_state", &RPCParser::parseServerInfo, 0, 1},
|
||||
{"sign", &RPCParser::parseSignSubmit, 2, 3},
|
||||
{"sign", &RPCParser::parseSignSubmit, 2, 4},
|
||||
{"sign_for", &RPCParser::parseSignFor, 3, 4},
|
||||
{"stop", &RPCParser::parseAsIs, 0, 0},
|
||||
{"simulate", &RPCParser::parseSimulate, 1, 2},
|
||||
{"submit", &RPCParser::parseSignSubmit, 1, 3},
|
||||
{"submit", &RPCParser::parseSignSubmit, 1, 4},
|
||||
{"submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1},
|
||||
{"transaction_entry", &RPCParser::parseTransactionEntry, 2, 2},
|
||||
{"tx", &RPCParser::parseTx, 1, 4},
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <xrpl/basics/mulDiv.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
@@ -54,6 +55,7 @@ private:
|
||||
AccountID const* const multiSigningAcctID_;
|
||||
std::optional<PublicKey> multiSignPublicKey_;
|
||||
Buffer multiSignature_;
|
||||
std::optional<std::reference_wrapper<SField const>> signatureTarget_;
|
||||
|
||||
public:
|
||||
explicit SigningForParams() : multiSigningAcctID_(nullptr)
|
||||
@@ -116,12 +118,25 @@ public:
|
||||
return multiSignature_;
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<SField const>> const&
|
||||
getSignatureTarget() const
|
||||
{
|
||||
return signatureTarget_;
|
||||
}
|
||||
|
||||
void
|
||||
setPublicKey(PublicKey const& multiSignPublicKey)
|
||||
{
|
||||
multiSignPublicKey_ = multiSignPublicKey;
|
||||
}
|
||||
|
||||
void
|
||||
setSignatureTarget(
|
||||
std::optional<std::reference_wrapper<SField const>> const& field)
|
||||
{
|
||||
signatureTarget_ = field;
|
||||
}
|
||||
|
||||
void
|
||||
moveMultiSignature(Buffer&& multiSignature)
|
||||
{
|
||||
@@ -427,6 +442,29 @@ transactionPreProcessImpl(
|
||||
bool const verify =
|
||||
!(params.isMember(jss::offline) && params[jss::offline].asBool());
|
||||
|
||||
auto const signatureTarget =
|
||||
[¶ms]() -> std::optional<std::reference_wrapper<SField const>> {
|
||||
if (params.isMember(jss::signature_target))
|
||||
return SField::getField(params[jss::signature_target].asString());
|
||||
return std::nullopt;
|
||||
}();
|
||||
|
||||
// Make sure the signature target field is valid, if specified, and save the
|
||||
// template for use later
|
||||
auto const signatureTemplate = signatureTarget
|
||||
? InnerObjectFormats::getInstance().findSOTemplateBySField(
|
||||
*signatureTarget)
|
||||
: nullptr;
|
||||
if (signatureTarget)
|
||||
{
|
||||
if (!signatureTemplate)
|
||||
{ // Invalid target field
|
||||
return RPC::make_error(
|
||||
rpcINVALID_PARAMS, signatureTarget->get().getName());
|
||||
}
|
||||
signingArgs.setSignatureTarget(signatureTarget);
|
||||
}
|
||||
|
||||
if (!params.isMember(jss::tx_json))
|
||||
return RPC::missing_field_error(jss::tx_json);
|
||||
|
||||
@@ -541,9 +579,10 @@ transactionPreProcessImpl(
|
||||
JLOG(j.trace()) << "verify: " << toBase58(calcAccountID(pk)) << " : "
|
||||
<< toBase58(srcAddressID);
|
||||
|
||||
// Don't do this test if multisigning since the account and secret
|
||||
// probably don't belong together in that case.
|
||||
if (!signingArgs.isMultiSigning())
|
||||
// Don't do this test if multisigning or if the signature is going into
|
||||
// an alternate field since the account and secret probably don't belong
|
||||
// together in that case.
|
||||
if (!signingArgs.isMultiSigning() && !signatureTarget)
|
||||
{
|
||||
// Make sure the account and secret belong together.
|
||||
if (tx_json.isMember(sfDelegate.jsonName))
|
||||
@@ -598,7 +637,17 @@ transactionPreProcessImpl(
|
||||
{
|
||||
// If we're generating a multi-signature the SigningPubKey must be
|
||||
// empty, otherwise it must be the master account's public key.
|
||||
parsed.object->setFieldVL(
|
||||
STObject* sigObject = &*parsed.object;
|
||||
if (signatureTarget)
|
||||
{
|
||||
// If the target object doesn't exist, make one.
|
||||
if (!parsed.object->isFieldPresent(*signatureTarget))
|
||||
parsed.object->setFieldObject(
|
||||
*signatureTarget,
|
||||
STObject{*signatureTemplate, *signatureTarget});
|
||||
sigObject = &parsed.object->peekFieldObject(*signatureTarget);
|
||||
}
|
||||
sigObject->setFieldVL(
|
||||
sfSigningPubKey,
|
||||
signingArgs.isMultiSigning() ? Slice(nullptr, 0) : pk.slice());
|
||||
|
||||
@@ -630,7 +679,7 @@ transactionPreProcessImpl(
|
||||
}
|
||||
else if (signingArgs.isSingleSigning())
|
||||
{
|
||||
stTx->sign(pk, sk);
|
||||
stTx->sign(pk, sk, signatureTarget);
|
||||
}
|
||||
|
||||
return transactionPreProcessResult{std::move(stTx)};
|
||||
@@ -1195,11 +1244,17 @@ transactionSignFor(
|
||||
signer.setFieldVL(
|
||||
sfSigningPubKey, signForParams.getPublicKey().slice());
|
||||
|
||||
STObject& sigTarget = [&]() -> STObject& {
|
||||
auto const target = signForParams.getSignatureTarget();
|
||||
if (target)
|
||||
return sttx->peekFieldObject(*target);
|
||||
return *sttx;
|
||||
}();
|
||||
// If there is not yet a Signers array, make one.
|
||||
if (!sttx->isFieldPresent(sfSigners))
|
||||
sttx->setFieldArray(sfSigners, {});
|
||||
if (!sigTarget.isFieldPresent(sfSigners))
|
||||
sigTarget.setFieldArray(sfSigners, {});
|
||||
|
||||
auto& signers = sttx->peekFieldArray(sfSigners);
|
||||
auto& signers = sigTarget.peekFieldArray(sfSigners);
|
||||
signers.emplace_back(std::move(signer));
|
||||
|
||||
// The array must be sorted and validated.
|
||||
|
||||
Reference in New Issue
Block a user