Disallow both single- and multi-signing in RPC (RIPD-1713):

The ledger already declared a transaction that is both single-
and multi-signing malformed.  This just adds some checking in
the signing RPC commands (like submit and sign_for) which allows
that sort of error to be identified a bit closer to the user.

In the process of adding this code a bug was found in the
RPCCall unit test.  That bug is fixed as well.
This commit is contained in:
Scott Schurr
2019-01-23 16:29:20 -08:00
committed by Nik Bougalis
parent d8c450d272
commit 36d6758945
5 changed files with 184 additions and 88 deletions

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Copyright (c) 2012 - 2019 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -81,6 +81,8 @@ enum error_code_i
// Bad parameter
rpcACT_BITCOIN,
rpcACT_MALFORMED,
rpcALREADY_MULTISIG,
rpcALREADY_SINGLE_SIG,
rpcQUALITY_MALFORMED,
rpcBAD_BLOB,
rpcBAD_FEATURE,
@@ -106,7 +108,7 @@ enum error_code_i
rpcPAYS_AMT_MALFORMED,
rpcPORT_MALFORMED,
rpcPUBLIC_MALFORMED,
rpcSIGN_FOR_MALFORMED,
rpcSIGNING_MALFORMED,
rpcSENDMAX_MALFORMED,
rpcSRC_ACT_MALFORMED,
rpcSRC_ACT_MISSING,

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Copyright (c) 2012 - 2019 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -54,6 +54,8 @@ public:
add (rpcACT_EXISTS, "actExists", "Account already exists.");
add (rpcACT_MALFORMED, "actMalformed", "Account malformed.");
add (rpcACT_NOT_FOUND, "actNotFound", "Account not found.");
add (rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned.");
add (rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed.");
add (rpcAMENDMENT_BLOCKED, "amendmentBlocked", "Amendment blocked, need upgrade.");
add (rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range.");
add (rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string.");
@@ -64,7 +66,7 @@ public:
add (rpcBAD_SEED, "badSeed", "Disallowed seed.");
add (rpcBAD_SYNTAX, "badSyntax", "Syntax error.");
add (rpcCHANNEL_MALFORMED, "channelMalformed", "Payment channel is malformed.");
add (rpcCHANNEL_AMT_MALFORMED, "channelAmtMalformed","Payment channel amount is malformed.");
add (rpcCHANNEL_AMT_MALFORMED, "channelAmtMalformed", "Payment channel amount is malformed.");
add (rpcCOMMAND_MISSING, "commandMissing", "Missing command entry.");
add (rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed.");
add (rpcDST_ACT_MISSING, "dstActMissing", "Destination account not provided.");
@@ -106,7 +108,7 @@ public:
add (rpcPORT_MALFORMED, "portMalformed", "Port is malformed.");
add (rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed.");
add (rpcQUALITY_MALFORMED, "qualityMalformed", "Quality malformed.");
add (rpcSIGN_FOR_MALFORMED, "signForMalformed", "Signing for account is malformed.");
add (rpcSIGNING_MALFORMED, "signingMalformed", "Signing of transaction is malformed.");
add (rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server.");
add (rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed.");
add (rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided.");

View File

@@ -80,6 +80,11 @@ public:
(multiSignature_ != nullptr));
}
bool isSingleSigning () const
{
return !isMultiSigning();
}
// When multi-signing we should not edit the tx_json fields.
bool editFields () const
{
@@ -447,9 +452,20 @@ transactionPreProcessImpl (
tx_json[jss::Flags] = tfFullyCanonicalSig;
}
// If multisigning then we need to return the public key.
// If multisigning there should not be a single signature and vice versa.
if (signingArgs.isMultiSigning())
{
if (tx_json.isMember (sfTxnSignature.jsonName))
return rpcError (rpcALREADY_SINGLE_SIG);
// If multisigning then we need to return the public key.
signingArgs.setPublicKey (keypair.first);
}
else if (signingArgs.isSingleSigning())
{
if (tx_json.isMember (sfSigners.jsonName))
return rpcError (rpcALREADY_MULTISIG);
}
if (verify)
{
@@ -524,7 +540,7 @@ transactionPreProcessImpl (
signingArgs.moveMultiSignature (std::move (multisig));
}
else
else if (signingArgs.isSingleSigning())
{
stpTrans->sign (keypair.first, keypair.second);
}
@@ -1131,6 +1147,10 @@ Json::Value transactionSubmitMultiSigned (
return RPC::make_error (rpcINVALID_PARAMS, err.str ());
}
// There may not be a TxnSignature field.
if (stpTrans->isFieldPresent (sfTxnSignature))
return rpcError (rpcSIGNING_MALFORMED);
// The Fee field must be in XRP and greater than zero.
auto const fee = stpTrans->getFieldAmount (sfFee);

View File

@@ -855,6 +855,36 @@ R"({
"Missing field 'tx_json.Sequence'.",
"Missing field 'tx_json.Sequence'."}}},
{ "Single-sign a multisigned transaction.", __LINE__,
R"({
"command": "doesnt_matter",
"account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"secret": "a",
"tx_json": {
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Amount" : "1000000000",
"Destination" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Fee" : "50",
"Sequence" : 0,
"Signers" : [
{
"Signer" : {
"Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
"TxnSignature" : "304502210080EB23E78A841DDC5E3A4F10DE6EAF052207D6B519BF8954467ADB221B3F349002202CA458E8D4E4DE7176D27A91628545E7B295A5DFC8ADF0B5CD3E279B6FA02998"
}
}
],
"SigningPubKey" : "",
"TransactionType" : "Payment"
}
})",
{{
"Already multisigned.",
"Already multisigned.",
"Secret does not match account.",
""}}},
{ "Minimal sign_for.", __LINE__,
R"({
"command": "doesnt_matter",
@@ -1109,8 +1139,8 @@ R"({
}
})",
{{
"Secret does not match account.",
"Secret does not match account.",
"Already multisigned.",
"Already multisigned.",
"Duplicate Signers:Signer:Account entries (rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh) are not allowed.",
""}}},
@@ -1139,8 +1169,8 @@ R"({
}
})",
{{
"Secret does not match account.",
"Secret does not match account.",
"Already multisigned.",
"Already multisigned.",
"",
""}}},
@@ -1169,8 +1199,8 @@ R"({
}
})",
{{
"Secret does not match account.",
"Secret does not match account.",
"Already multisigned.",
"Already multisigned.",
"Invalid signature.",
"Invalid signature."}}},
@@ -1215,6 +1245,37 @@ R"({
"Missing field 'tx_json.TransactionType'.",
"Missing field 'tx_json.TransactionType'."}}},
{ "TxnSignature in sign_for.", __LINE__,
R"({
"command": "doesnt_matter",
"account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"secret": "c",
"tx_json": {
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Amount" : "1000000000",
"Destination" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Fee" : "50",
"Sequence" : 0,
"Signers" : [
{
"Signer" : {
"Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
"TxnSignature" : "304502210080EB23E78A841DDC5E3A4F10DE6EAF052207D6B519BF8954467ADB221B3F349002202CA458E8D4E4DE7176D27A91628545E7B295A5DFC8ADF0B5CD3E279B6FA02998"
}
}
],
"SigningPubKey" : "",
"TxnSignature" : "304502210080EB23E78A841DDC5E3A4F10DE6EAF052207D6B519BF8954467ADB221B3F349002202CA458E8D4E4DE7176D27A91628545E7B295A5DFC8ADF0B5CD3E279B6FA02998",
"TransactionType" : "Payment"
}
})",
{{
"Already multisigned.",
"Already multisigned.",
"Already single-signed.",
"Signing of transaction is malformed."}}},
{ "Invalid field 'tx_json': string instead of object", __LINE__,
R"({
"command": "doesnt_matter",
@@ -2337,7 +2398,8 @@ public:
{
std::ostringstream description;
description << txnTest.description << " Called "
<< get<2>(testFunc) << "()";
<< get<2>(testFunc) << "(). Got \'"
<< errStr << "\'";
fail (description.str(), __FILE__, txnTest.line);
}
}

View File

@@ -6970,10 +6970,20 @@ public:
Json::Value exp;
Json::Reader{}.parse (rpcCallTest.exp, exp);
// If there is an "error_code" field, remove it. Error codes
// are not expected to stay stable between releases.
got.removeMember ("error_code");
exp.removeMember ("error_code");
// Lambda to remove the "params[0u]:error_code" field if present.
// Error codes are not expected to be stable between releases.
auto rmErrorCode = [] (Json::Value& json)
{
if (json.isMember (jss::params) &&
json[jss::params].isArray() &&
json[jss::params].size() > 0 &&
json[jss::params][0u].isObject())
{
json[jss::params][0u].removeMember (jss::error_code);
}
};
rmErrorCode (got);
rmErrorCode (exp);
// Pass if we didn't expect a throw and we got what we expected.
if ((rpcCallTest.throwsWhat == RPCCallTestData::no_exception) &&