mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 19:45:53 +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/ByteUtilities.h>
|
||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
#include <xrpl/basics/partitioned_unordered_map.h>
|
#include <xrpl/basics/partitioned_unordered_map.h>
|
||||||
|
#include <xrpl/protocol/Units.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
|||||||
@@ -389,6 +389,8 @@ public:
|
|||||||
setFieldV256(SField const& field, STVector256 const& v);
|
setFieldV256(SField const& field, STVector256 const& v);
|
||||||
void
|
void
|
||||||
setFieldArray(SField const& field, STArray const& v);
|
setFieldArray(SField const& field, STArray const& v);
|
||||||
|
void
|
||||||
|
setFieldObject(SField const& field, STObject const& v);
|
||||||
|
|
||||||
template <class Tag>
|
template <class Tag>
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -125,7 +125,11 @@ public:
|
|||||||
getJson(JsonOptions options, bool binary) const;
|
getJson(JsonOptions options, bool binary) const;
|
||||||
|
|
||||||
void
|
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 };
|
enum class RequireFullyCanonicalSig : bool { no, yes };
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ JSS(BaseAsset); // in: Oracle
|
|||||||
JSS(BidMax); // in: AMM Bid
|
JSS(BidMax); // in: AMM Bid
|
||||||
JSS(BidMin); // in: AMM Bid
|
JSS(BidMin); // in: AMM Bid
|
||||||
JSS(ClearFlag); // field.
|
JSS(ClearFlag); // field.
|
||||||
|
JSS(Counterparty); // field.
|
||||||
JSS(CounterpartySignature);// field.
|
JSS(CounterpartySignature);// field.
|
||||||
JSS(DeliverMax); // out: alias to Amount
|
JSS(DeliverMax); // out: alias to Amount
|
||||||
JSS(DeliverMin); // in: TransactionSign
|
JSS(DeliverMin); // in: TransactionSign
|
||||||
@@ -567,6 +568,7 @@ JSS(settle_delay); // out: AccountChannels
|
|||||||
JSS(severity); // in: LogLevel
|
JSS(severity); // in: LogLevel
|
||||||
JSS(shares); // out: VaultInfo
|
JSS(shares); // out: VaultInfo
|
||||||
JSS(signature); // out: NetworkOPs, ChannelAuthorize
|
JSS(signature); // out: NetworkOPs, ChannelAuthorize
|
||||||
|
JSS(signature_target); // in: TransactionSign
|
||||||
JSS(signature_verified); // out: ChannelVerify
|
JSS(signature_verified); // out: ChannelVerify
|
||||||
JSS(signing_key); // out: NetworkOPs
|
JSS(signing_key); // out: NetworkOPs
|
||||||
JSS(signing_keys); // out: ValidatorList
|
JSS(signing_keys); // out: ValidatorList
|
||||||
|
|||||||
@@ -831,6 +831,12 @@ STObject::setFieldArray(SField const& field, STArray const& v)
|
|||||||
setFieldUsingAssignment(field, v);
|
setFieldUsingAssignment(field, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
STObject::setFieldObject(SField const& field, STObject const& v)
|
||||||
|
{
|
||||||
|
setFieldUsingAssignment(field, v);
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value
|
Json::Value
|
||||||
STObject::getJson(JsonOptions options) const
|
STObject::getJson(JsonOptions options) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -234,13 +234,24 @@ STTx::getSeqValue() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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 data = getSigningData(*this);
|
||||||
|
|
||||||
auto const sig = ripple::sign(publicKey, secretKey, makeSlice(data));
|
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);
|
tid_ = getHash(HashPrefix::transactionID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include <xrpl/basics/base_uint.h>
|
#include <xrpl/basics/base_uint.h>
|
||||||
#include <xrpl/beast/unit_test/suite.h>
|
#include <xrpl/beast/unit_test/suite.h>
|
||||||
#include <xrpl/json/json_forwards.h>
|
#include <xrpl/json/json_forwards.h>
|
||||||
|
#include <xrpl/json/json_reader.h>
|
||||||
#include <xrpl/protocol/Asset.h>
|
#include <xrpl/protocol/Asset.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
#include <xrpl/protocol/Feature.h>
|
||||||
#include <xrpl/protocol/Indexes.h>
|
#include <xrpl/protocol/Indexes.h>
|
||||||
@@ -2162,31 +2163,301 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
Env env(*this, all);
|
Env env(*this, all);
|
||||||
|
|
||||||
Account const alice{"alice"};
|
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();
|
testcase("RPC AccountSet");
|
||||||
auto const jr = env.rpc(
|
Json::Value txJson{Json::objectValue};
|
||||||
"sign",
|
txJson[sfTransactionType] = "AccountSet";
|
||||||
encodeBase58Token(TokenType::FamilySeed, sk.data(), sk.size()),
|
txJson[sfAccount] = borrower.human();
|
||||||
R"({ "TransactionType" : "LoanSet", )"
|
|
||||||
R"("Account" : "rHP9W1SByc8on4vfFsBdt5sun2gZTnBhkx", )"
|
auto const signParams = [&]() {
|
||||||
R"("Counterparty" : "rHP9W1SByc8on4vfFsBdt5sun2gZTnBhkx", )"
|
Json::Value signParams{Json::objectValue};
|
||||||
R"("LoanBrokerID" : )"
|
signParams[jss::passphrase] = borrowerPass;
|
||||||
R"("EAFD2D37FE12815F00705F1B57173A16F94EE15E02D53AF5B683942B57ED53E9", )"
|
signParams[jss::key_type] = "ed25519";
|
||||||
R"("PrincipalRequested" : "100000000", )"
|
signParams[jss::tx_json] = txJson;
|
||||||
R"("StartDate" : "807730340", "PaymentTotal" : 10000, )"
|
return signParams;
|
||||||
R"("PaymentInterval" : 1, "GracePeriod" : 300, )"
|
}();
|
||||||
R"("Flags" : 65536, "Fee" : "24", "Sequence" : 1 })",
|
auto const jSign = env.rpc("json", "sign", to_string(signParams));
|
||||||
"offline");
|
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(
|
BEAST_EXPECT(
|
||||||
jr.isMember(jss::result) &&
|
!jSubmitBlob.isMember(jss::error) &&
|
||||||
jr[jss::result].isMember(jss::tx_json));
|
!jSubmitBlobResult.isMember(jss::error));
|
||||||
auto const& tx = jr[jss::result][jss::tx_json];
|
|
||||||
BEAST_EXPECT(!tx.isMember(jss::CounterpartySignature));
|
// 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(
|
BEAST_EXPECT(
|
||||||
tx.isMember(jss::TxnSignature) &&
|
jSubmitJsonResult.isMember(jss::engine_result) &&
|
||||||
tx[jss::TxnSignature].asString().length() == 142);
|
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"
|
" server_state [counters]\n"
|
||||||
" sign <private_key> <tx_json> [offline]\n"
|
" sign <private_key> <tx_json> [offline]\n"
|
||||||
" sign_for <signer_address> <signer_private_key> <tx_json> "
|
" sign_for <signer_address> <signer_private_key> <tx_json> "
|
||||||
"[offline]\n"
|
"[offline] [<signature_field>]\n"
|
||||||
" stop\n"
|
" stop\n"
|
||||||
" simulate [<tx_blob>|<tx_json>] [<binary>]\n"
|
" simulate [<tx_blob>|<tx_json>] [<binary>]\n"
|
||||||
" submit <tx_blob>|[<private_key> <tx_json>]\n"
|
" submit <tx_blob>|[<private_key> <tx_json>]\n"
|
||||||
|
|||||||
@@ -966,7 +966,16 @@ private:
|
|||||||
Json::Value txJSON;
|
Json::Value txJSON;
|
||||||
Json::Reader reader;
|
Json::Reader reader;
|
||||||
bool const bOffline =
|
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())
|
if (1 == jvParams.size())
|
||||||
{
|
{
|
||||||
@@ -979,7 +988,7 @@ private:
|
|||||||
return jvRequest;
|
return jvRequest;
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
(2 == jvParams.size() || bOffline) &&
|
(jvParams.size() >= 2 || bOffline) &&
|
||||||
reader.parse(jvParams[1u].asString(), txJSON))
|
reader.parse(jvParams[1u].asString(), txJSON))
|
||||||
{
|
{
|
||||||
// Signing or submitting tx_json.
|
// Signing or submitting tx_json.
|
||||||
@@ -991,6 +1000,9 @@ private:
|
|||||||
if (bOffline)
|
if (bOffline)
|
||||||
jvRequest[jss::offline] = true;
|
jvRequest[jss::offline] = true;
|
||||||
|
|
||||||
|
if (field)
|
||||||
|
jvRequest[jss::signature_target] = *field;
|
||||||
|
|
||||||
return jvRequest;
|
return jvRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <xrpl/basics/mulDiv.h>
|
#include <xrpl/basics/mulDiv.h>
|
||||||
#include <xrpl/json/json_writer.h>
|
#include <xrpl/json/json_writer.h>
|
||||||
#include <xrpl/protocol/ErrorCodes.h>
|
#include <xrpl/protocol/ErrorCodes.h>
|
||||||
|
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||||
#include <xrpl/protocol/RPCErr.h>
|
#include <xrpl/protocol/RPCErr.h>
|
||||||
#include <xrpl/protocol/STParsedJSON.h>
|
#include <xrpl/protocol/STParsedJSON.h>
|
||||||
#include <xrpl/protocol/Sign.h>
|
#include <xrpl/protocol/Sign.h>
|
||||||
@@ -54,6 +55,7 @@ private:
|
|||||||
AccountID const* const multiSigningAcctID_;
|
AccountID const* const multiSigningAcctID_;
|
||||||
std::optional<PublicKey> multiSignPublicKey_;
|
std::optional<PublicKey> multiSignPublicKey_;
|
||||||
Buffer multiSignature_;
|
Buffer multiSignature_;
|
||||||
|
std::optional<std::reference_wrapper<SField const>> signatureTarget_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SigningForParams() : multiSigningAcctID_(nullptr)
|
explicit SigningForParams() : multiSigningAcctID_(nullptr)
|
||||||
@@ -116,12 +118,25 @@ public:
|
|||||||
return multiSignature_;
|
return multiSignature_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::reference_wrapper<SField const>> const&
|
||||||
|
getSignatureTarget() const
|
||||||
|
{
|
||||||
|
return signatureTarget_;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
setPublicKey(PublicKey const& multiSignPublicKey)
|
setPublicKey(PublicKey const& multiSignPublicKey)
|
||||||
{
|
{
|
||||||
multiSignPublicKey_ = multiSignPublicKey;
|
multiSignPublicKey_ = multiSignPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
setSignatureTarget(
|
||||||
|
std::optional<std::reference_wrapper<SField const>> const& field)
|
||||||
|
{
|
||||||
|
signatureTarget_ = field;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
moveMultiSignature(Buffer&& multiSignature)
|
moveMultiSignature(Buffer&& multiSignature)
|
||||||
{
|
{
|
||||||
@@ -427,6 +442,29 @@ transactionPreProcessImpl(
|
|||||||
bool const verify =
|
bool const verify =
|
||||||
!(params.isMember(jss::offline) && params[jss::offline].asBool());
|
!(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))
|
if (!params.isMember(jss::tx_json))
|
||||||
return RPC::missing_field_error(jss::tx_json);
|
return RPC::missing_field_error(jss::tx_json);
|
||||||
|
|
||||||
@@ -541,9 +579,10 @@ transactionPreProcessImpl(
|
|||||||
JLOG(j.trace()) << "verify: " << toBase58(calcAccountID(pk)) << " : "
|
JLOG(j.trace()) << "verify: " << toBase58(calcAccountID(pk)) << " : "
|
||||||
<< toBase58(srcAddressID);
|
<< toBase58(srcAddressID);
|
||||||
|
|
||||||
// Don't do this test if multisigning since the account and secret
|
// Don't do this test if multisigning or if the signature is going into
|
||||||
// probably don't belong together in that case.
|
// an alternate field since the account and secret probably don't belong
|
||||||
if (!signingArgs.isMultiSigning())
|
// together in that case.
|
||||||
|
if (!signingArgs.isMultiSigning() && !signatureTarget)
|
||||||
{
|
{
|
||||||
// Make sure the account and secret belong together.
|
// Make sure the account and secret belong together.
|
||||||
if (tx_json.isMember(sfDelegate.jsonName))
|
if (tx_json.isMember(sfDelegate.jsonName))
|
||||||
@@ -598,7 +637,17 @@ transactionPreProcessImpl(
|
|||||||
{
|
{
|
||||||
// If we're generating a multi-signature the SigningPubKey must be
|
// If we're generating a multi-signature the SigningPubKey must be
|
||||||
// empty, otherwise it must be the master account's public key.
|
// 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,
|
sfSigningPubKey,
|
||||||
signingArgs.isMultiSigning() ? Slice(nullptr, 0) : pk.slice());
|
signingArgs.isMultiSigning() ? Slice(nullptr, 0) : pk.slice());
|
||||||
|
|
||||||
@@ -630,7 +679,7 @@ transactionPreProcessImpl(
|
|||||||
}
|
}
|
||||||
else if (signingArgs.isSingleSigning())
|
else if (signingArgs.isSingleSigning())
|
||||||
{
|
{
|
||||||
stTx->sign(pk, sk);
|
stTx->sign(pk, sk, signatureTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
return transactionPreProcessResult{std::move(stTx)};
|
return transactionPreProcessResult{std::move(stTx)};
|
||||||
@@ -1195,11 +1244,17 @@ transactionSignFor(
|
|||||||
signer.setFieldVL(
|
signer.setFieldVL(
|
||||||
sfSigningPubKey, signForParams.getPublicKey().slice());
|
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 there is not yet a Signers array, make one.
|
||||||
if (!sttx->isFieldPresent(sfSigners))
|
if (!sigTarget.isFieldPresent(sfSigners))
|
||||||
sttx->setFieldArray(sfSigners, {});
|
sigTarget.setFieldArray(sfSigners, {});
|
||||||
|
|
||||||
auto& signers = sttx->peekFieldArray(sfSigners);
|
auto& signers = sigTarget.peekFieldArray(sfSigners);
|
||||||
signers.emplace_back(std::move(signer));
|
signers.emplace_back(std::move(signer));
|
||||||
|
|
||||||
// The array must be sorted and validated.
|
// The array must be sorted and validated.
|
||||||
|
|||||||
Reference in New Issue
Block a user