mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
Add Counterparty signing support
- Add a new parameter "signature_target" to "sign". Supports single and multisign, but I haven't written tests for multisign yet. - Skip account validation if this field is set, like multisigning. - Unit tests demonstrating examples.
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/partitioned_unordered_map.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -389,6 +389,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
|
||||
|
||||
@@ -125,7 +125,11 @@ 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 =
|
||||
{});
|
||||
|
||||
enum class RequireFullyCanonicalSig : bool { no, yes };
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ JSS(BaseAsset); // in: Oracle
|
||||
JSS(BidMax); // in: AMM Bid
|
||||
JSS(BidMin); // in: AMM Bid
|
||||
JSS(ClearFlag); // field.
|
||||
JSS(Counterparty); // field.
|
||||
JSS(CounterpartySignature);// field.
|
||||
JSS(DeliverMax); // out: alias to Amount
|
||||
JSS(DeliverMin); // in: TransactionSign
|
||||
@@ -567,6 +568,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
|
||||
|
||||
@@ -831,6 +831,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
|
||||
{
|
||||
|
||||
@@ -234,13 +234,24 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_forwards.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -2162,31 +2163,301 @@ class Loan_test : public beast::unit_test::suite
|
||||
Env env(*this, all);
|
||||
|
||||
Account const alice{"alice"};
|
||||
std::string const borrowerPass = "borrower";
|
||||
std::string const borrowerSeed = "ssBRAsLpH4778sLNYC4ik1JBJsBVf";
|
||||
Account borrower{borrowerPass, KeyType::ed25519};
|
||||
auto const lenderPass = "lender";
|
||||
std::string const lenderSeed = "shPTCZGwTEhJrYT8NbcNkeaa8pzPM";
|
||||
Account lender{lenderPass, KeyType::ed25519};
|
||||
|
||||
env.fund(XRP(1'000'000), alice, lender, borrower);
|
||||
env.close();
|
||||
env(noop(lender));
|
||||
env(noop(lender));
|
||||
env(noop(lender));
|
||||
env(noop(lender));
|
||||
env(noop(lender));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const sk = alice.sk();
|
||||
auto const jr = env.rpc(
|
||||
"sign",
|
||||
encodeBase58Token(TokenType::FamilySeed, sk.data(), sk.size()),
|
||||
R"({ "TransactionType" : "LoanSet", )"
|
||||
R"("Account" : "rHP9W1SByc8on4vfFsBdt5sun2gZTnBhkx", )"
|
||||
R"("Counterparty" : "rHP9W1SByc8on4vfFsBdt5sun2gZTnBhkx", )"
|
||||
R"("LoanBrokerID" : )"
|
||||
R"("EAFD2D37FE12815F00705F1B57173A16F94EE15E02D53AF5B683942B57ED53E9", )"
|
||||
R"("PrincipalRequested" : "100000000", )"
|
||||
R"("StartDate" : "807730340", "PaymentTotal" : 10000, )"
|
||||
R"("PaymentInterval" : 1, "GracePeriod" : 300, )"
|
||||
R"("Flags" : 65536, "Fee" : "24", "Sequence" : 1 })",
|
||||
"offline");
|
||||
testcase("RPC AccountSet");
|
||||
Json::Value txJson{Json::objectValue};
|
||||
txJson[sfTransactionType] = "AccountSet";
|
||||
txJson[sfAccount] = borrower.human();
|
||||
|
||||
auto const signParams = [&]() {
|
||||
Json::Value signParams{Json::objectValue};
|
||||
signParams[jss::passphrase] = borrowerPass;
|
||||
signParams[jss::key_type] = "ed25519";
|
||||
signParams[jss::tx_json] = txJson;
|
||||
return signParams;
|
||||
}();
|
||||
auto const jSign = env.rpc("json", "sign", to_string(signParams));
|
||||
BEAST_EXPECT(
|
||||
jSign.isMember(jss::result) &&
|
||||
jSign[jss::result].isMember(jss::tx_json));
|
||||
auto txSignResult = jSign[jss::result][jss::tx_json];
|
||||
auto txSignBlob = jSign[jss::result][jss::tx_blob].asString();
|
||||
txSignResult.removeMember(jss::hash);
|
||||
|
||||
auto const jtx = env.jt(txJson, sig(borrower));
|
||||
BEAST_EXPECT(txSignResult == jtx.jv);
|
||||
|
||||
auto const jSubmit = env.rpc("submit", txSignBlob);
|
||||
BEAST_EXPECT(
|
||||
jSubmit.isMember(jss::result) &&
|
||||
jSubmit[jss::result].isMember(jss::engine_result) &&
|
||||
jSubmit[jss::result][jss::engine_result].asString() ==
|
||||
"tesSUCCESS");
|
||||
|
||||
env(jtx.jv, sig(none), seq(none), fee(none), ter(tefPAST_SEQ));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("RPC LoanSet - illegal signature_target");
|
||||
|
||||
Json::Value txJson{Json::objectValue};
|
||||
txJson[sfTransactionType] = "AccountSet";
|
||||
txJson[sfAccount] = borrower.human();
|
||||
|
||||
auto const borrowerSignParams = [&]() {
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::passphrase] = borrowerPass;
|
||||
params[jss::key_type] = "ed25519";
|
||||
params[jss::signature_target] = "Destination";
|
||||
params[jss::tx_json] = txJson;
|
||||
return params;
|
||||
}();
|
||||
auto const jSignBorrower =
|
||||
env.rpc("json", "sign", to_string(borrowerSignParams));
|
||||
BEAST_EXPECT(
|
||||
jSignBorrower.isMember(jss::result) &&
|
||||
jSignBorrower[jss::result].isMember(jss::error) &&
|
||||
jSignBorrower[jss::result][jss::error] == "invalidParams" &&
|
||||
jSignBorrower[jss::result].isMember(jss::error_message) &&
|
||||
jSignBorrower[jss::result][jss::error_message] ==
|
||||
"Destination");
|
||||
}
|
||||
{
|
||||
testcase("RPC LoanSet - sign and submit borrower initiated");
|
||||
// 1. Borrower creates the transaction
|
||||
Json::Value txJson{Json::objectValue};
|
||||
txJson[sfTransactionType] = "LoanSet";
|
||||
txJson[sfAccount] = borrower.human();
|
||||
txJson[sfCounterparty] = lender.human();
|
||||
txJson[sfLoanBrokerID] =
|
||||
"FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FECF83F"
|
||||
"5C";
|
||||
txJson[sfPrincipalRequested] = "100000000";
|
||||
txJson[sfStartDate] = 807730340;
|
||||
txJson[sfPaymentTotal] = 10000;
|
||||
txJson[sfPaymentInterval] = 3600;
|
||||
txJson[sfGracePeriod] = 300;
|
||||
txJson[sfFlags] = 65536; // tfLoanOverpayment
|
||||
txJson[sfFee] = "24";
|
||||
|
||||
// 2. Borrower signs the transaction
|
||||
auto const borrowerSignParams = [&]() {
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::passphrase] = borrowerPass;
|
||||
params[jss::key_type] = "ed25519";
|
||||
params[jss::tx_json] = txJson;
|
||||
return params;
|
||||
}();
|
||||
auto const jSignBorrower =
|
||||
env.rpc("json", "sign", to_string(borrowerSignParams));
|
||||
BEAST_EXPECT(
|
||||
jSignBorrower.isMember(jss::result) &&
|
||||
jSignBorrower[jss::result].isMember(jss::tx_json));
|
||||
auto const txBorrowerSignResult =
|
||||
jSignBorrower[jss::result][jss::tx_json];
|
||||
auto const txBorrowerSignBlob =
|
||||
jSignBorrower[jss::result][jss::tx_blob].asString();
|
||||
|
||||
// 2a. Borrower attempts to submit the transaction. It doesn't work
|
||||
{
|
||||
auto const jSubmitBlob = env.rpc("submit", txBorrowerSignBlob);
|
||||
BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
|
||||
auto const jSubmitBlobResult = jSubmitBlob[jss::result];
|
||||
BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
|
||||
// Transaction fails because the CounterpartySignature is
|
||||
// missing
|
||||
BEAST_EXPECT(
|
||||
jSubmitBlobResult.isMember(jss::engine_result) &&
|
||||
jSubmitBlobResult[jss::engine_result].asString() ==
|
||||
"temBAD_SIGNER");
|
||||
}
|
||||
|
||||
// 3. Borrower sends the signed transaction to the lender
|
||||
// 4. Lender signs the transaction
|
||||
auto const lenderSignParams = [&]() {
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::passphrase] = lenderPass;
|
||||
params[jss::key_type] = "ed25519";
|
||||
params[jss::signature_target] = "CounterpartySignature";
|
||||
params[jss::tx_json] = txBorrowerSignResult;
|
||||
return params;
|
||||
}();
|
||||
auto const jSignLender =
|
||||
env.rpc("json", "sign", to_string(lenderSignParams));
|
||||
BEAST_EXPECT(
|
||||
jSignLender.isMember(jss::result) &&
|
||||
jSignLender[jss::result].isMember(jss::tx_json));
|
||||
auto const txLenderSignResult =
|
||||
jSignLender[jss::result][jss::tx_json];
|
||||
auto const txLenderSignBlob =
|
||||
jSignLender[jss::result][jss::tx_blob].asString();
|
||||
|
||||
// 5. Lender submits the signed transaction blob
|
||||
auto const jSubmitBlob = env.rpc("submit", txLenderSignBlob);
|
||||
BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
|
||||
auto const jSubmitBlobResult = jSubmitBlob[jss::result];
|
||||
BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
|
||||
auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
|
||||
// To get far enough to return tecNO_ENTRY means that the signatures
|
||||
// all validated. Of course the transaction won't succeed because no
|
||||
// Vault or Broker were created.
|
||||
BEAST_EXPECT(
|
||||
jSubmitBlobResult.isMember(jss::engine_result) &&
|
||||
jSubmitBlobResult[jss::engine_result].asString() ==
|
||||
"tecNO_ENTRY");
|
||||
|
||||
BEAST_EXPECT(
|
||||
jr.isMember(jss::result) &&
|
||||
jr[jss::result].isMember(jss::tx_json));
|
||||
auto const& tx = jr[jss::result][jss::tx_json];
|
||||
BEAST_EXPECT(!tx.isMember(jss::CounterpartySignature));
|
||||
!jSubmitBlob.isMember(jss::error) &&
|
||||
!jSubmitBlobResult.isMember(jss::error));
|
||||
|
||||
// 4-alt. Lender submits the transaction json originally received
|
||||
// from the Borrower. It gets signed, but is now a duplicate, so
|
||||
// fails. Borrower could done this instead of steps 4 and 5.
|
||||
auto const jSubmitJson =
|
||||
env.rpc("json", "submit", to_string(lenderSignParams));
|
||||
BEAST_EXPECT(jSubmitJson.isMember(jss::result));
|
||||
auto const jSubmitJsonResult = jSubmitJson[jss::result];
|
||||
BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
|
||||
auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
|
||||
// Since the previous tx claimed a fee, this duplicate is not going
|
||||
// anywhere
|
||||
BEAST_EXPECT(
|
||||
tx.isMember(jss::TxnSignature) &&
|
||||
tx[jss::TxnSignature].asString().length() == 142);
|
||||
jSubmitJsonResult.isMember(jss::engine_result) &&
|
||||
jSubmitJsonResult[jss::engine_result].asString() ==
|
||||
"tefPAST_SEQ");
|
||||
|
||||
BEAST_EXPECT(
|
||||
!jSubmitJson.isMember(jss::error) &&
|
||||
!jSubmitJsonResult.isMember(jss::error));
|
||||
|
||||
BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("RPC LoanSet - sign and submit lender initiated");
|
||||
// 1. Lender creates the transaction
|
||||
Json::Value txJson{Json::objectValue};
|
||||
txJson[sfTransactionType] = "LoanSet";
|
||||
txJson[sfAccount] = lender.human();
|
||||
txJson[sfCounterparty] = borrower.human();
|
||||
txJson[sfLoanBrokerID] =
|
||||
"FF924CD18A236C2B49CF8E80A351CEAC6A10171DC9F110025646894FECF83F"
|
||||
"5C";
|
||||
txJson[sfPrincipalRequested] = "100000000";
|
||||
txJson[sfStartDate] = 807730340;
|
||||
txJson[sfPaymentTotal] = 10000;
|
||||
txJson[sfPaymentInterval] = 3600;
|
||||
txJson[sfGracePeriod] = 300;
|
||||
txJson[sfFlags] = 65536; // tfLoanOverpayment
|
||||
txJson[sfFee] = "24";
|
||||
|
||||
// 2. Lender signs the transaction
|
||||
auto const lenderSignParams = [&]() {
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::passphrase] = lenderPass;
|
||||
params[jss::key_type] = "ed25519";
|
||||
params[jss::tx_json] = txJson;
|
||||
return params;
|
||||
}();
|
||||
auto const jSignLender =
|
||||
env.rpc("json", "sign", to_string(lenderSignParams));
|
||||
BEAST_EXPECT(
|
||||
jSignLender.isMember(jss::result) &&
|
||||
jSignLender[jss::result].isMember(jss::tx_json));
|
||||
auto const txLenderSignResult =
|
||||
jSignLender[jss::result][jss::tx_json];
|
||||
auto const txLenderSignBlob =
|
||||
jSignLender[jss::result][jss::tx_blob].asString();
|
||||
|
||||
// 2a. Lender attempts to submit the transaction. It doesn't work
|
||||
{
|
||||
auto const jSubmitBlob = env.rpc("submit", txLenderSignBlob);
|
||||
BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
|
||||
auto const jSubmitBlobResult = jSubmitBlob[jss::result];
|
||||
BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
|
||||
// Transaction fails because the CounterpartySignature is
|
||||
// missing
|
||||
BEAST_EXPECT(
|
||||
jSubmitBlobResult.isMember(jss::engine_result) &&
|
||||
jSubmitBlobResult[jss::engine_result].asString() ==
|
||||
"temBAD_SIGNER");
|
||||
}
|
||||
|
||||
// 3. Lender sends the signed transaction to the Borrower
|
||||
// 4. Borrower signs the transaction
|
||||
auto const borrowerSignParams = [&]() {
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::passphrase] = borrowerPass;
|
||||
params[jss::key_type] = "ed25519";
|
||||
params[jss::signature_target] = "CounterpartySignature";
|
||||
params[jss::tx_json] = txLenderSignResult;
|
||||
return params;
|
||||
}();
|
||||
auto const jSignBorrower =
|
||||
env.rpc("json", "sign", to_string(borrowerSignParams));
|
||||
BEAST_EXPECT(
|
||||
jSignBorrower.isMember(jss::result) &&
|
||||
jSignBorrower[jss::result].isMember(jss::tx_json));
|
||||
auto const txBorrowerSignResult =
|
||||
jSignBorrower[jss::result][jss::tx_json];
|
||||
auto const txBorrowerSignBlob =
|
||||
jSignBorrower[jss::result][jss::tx_blob].asString();
|
||||
|
||||
// 5. Borrower submits the signed transaction blob
|
||||
auto const jSubmitBlob = env.rpc("submit", txBorrowerSignBlob);
|
||||
BEAST_EXPECT(jSubmitBlob.isMember(jss::result));
|
||||
auto const jSubmitBlobResult = jSubmitBlob[jss::result];
|
||||
BEAST_EXPECT(jSubmitBlobResult.isMember(jss::tx_json));
|
||||
auto const jSubmitBlobTx = jSubmitBlobResult[jss::tx_json];
|
||||
// To get far enough to return tecNO_ENTRY means that the signatures
|
||||
// all validated. Of course the transaction won't succeed because no
|
||||
// Vault or Broker were created.
|
||||
BEAST_EXPECT(
|
||||
jSubmitBlobResult.isMember(jss::engine_result) &&
|
||||
jSubmitBlobResult[jss::engine_result].asString() ==
|
||||
"tecNO_ENTRY");
|
||||
|
||||
BEAST_EXPECT(
|
||||
!jSubmitBlob.isMember(jss::error) &&
|
||||
!jSubmitBlobResult.isMember(jss::error));
|
||||
|
||||
// 4-alt. Borrower submits the transaction json originally received
|
||||
// from the Lender. It gets signed, but is now a duplicate, so
|
||||
// fails. Lender could done this instead of steps 4 and 5.
|
||||
auto const jSubmitJson =
|
||||
env.rpc("json", "submit", to_string(borrowerSignParams));
|
||||
BEAST_EXPECT(jSubmitJson.isMember(jss::result));
|
||||
auto const jSubmitJsonResult = jSubmitJson[jss::result];
|
||||
BEAST_EXPECT(jSubmitJsonResult.isMember(jss::tx_json));
|
||||
auto const jSubmitJsonTx = jSubmitJsonResult[jss::tx_json];
|
||||
// Since the previous tx claimed a fee, this duplicate is not going
|
||||
// anywhere
|
||||
BEAST_EXPECT(
|
||||
jSubmitJsonResult.isMember(jss::engine_result) &&
|
||||
jSubmitJsonResult[jss::engine_result].asString() ==
|
||||
"tefPAST_SEQ");
|
||||
|
||||
BEAST_EXPECT(
|
||||
!jSubmitJson.isMember(jss::error) &&
|
||||
!jSubmitJsonResult.isMember(jss::error));
|
||||
|
||||
BEAST_EXPECT(jSubmitBlobTx == jSubmitJsonTx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +168,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"
|
||||
|
||||
@@ -966,7 +966,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())
|
||||
{
|
||||
@@ -979,7 +988,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.
|
||||
@@ -991,6 +1000,9 @@ private:
|
||||
if (bOffline)
|
||||
jvRequest[jss::offline] = true;
|
||||
|
||||
if (field)
|
||||
jvRequest[jss::signature_target] = *field;
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
|
||||
@@ -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