diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index 6a15ae833d..0b27ae1b0d 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -717,23 +717,43 @@ private: return parseAccountRaw2 (jvParams, jss::destination_account); } - // channel_authorize + // channel_authorize: [] Json::Value parseChannelAuthorize (Json::Value const& jvParams) { Json::Value jvRequest (Json::objectValue); - jvRequest[jss::secret] = jvParams[0u]; + unsigned int index = 0; + + if (jvParams.size() == 4) + { + jvRequest[jss::passphrase] = jvParams[index]; + index++; + + if (!keyTypeFromString(jvParams[index].asString())) + return rpcError (rpcBAD_KEY_TYPE); + jvRequest[jss::key_type] = jvParams[index]; + index++; + } + else + { + jvRequest[jss::secret] = jvParams[index]; + index++; + } + { // verify the channel id is a valid 256 bit number uint256 channelId; - if (!channelId.SetHexExact (jvParams[1u].asString ())) + if (!channelId.SetHexExact (jvParams[index].asString())) return rpcError (rpcCHANNEL_MALFORMED); + jvRequest[jss::channel_id] = to_string(channelId); + index++; } - jvRequest[jss::channel_id] = jvParams[1u].asString (); - if (!jvParams[2u].isString() || !to_uint64(jvParams[2u].asString())) + if (!jvParams[index].isString() || !to_uint64(jvParams[index].asString())) return rpcError(rpcCHANNEL_AMT_MALFORMED); - jvRequest[jss::amount] = jvParams[2u]; + jvRequest[jss::amount] = jvParams[index]; + + // If additional parameters are appended, be sure to increment index here return jvRequest; } @@ -1122,7 +1142,7 @@ public: { "account_tx", &RPCParser::parseAccountTransactions, 1, 8 }, { "book_offers", &RPCParser::parseBookOffers, 2, 7 }, { "can_delete", &RPCParser::parseCanDelete, 0, 1 }, - { "channel_authorize", &RPCParser::parseChannelAuthorize, 3, 3 }, + { "channel_authorize", &RPCParser::parseChannelAuthorize, 3, 4 }, { "channel_verify", &RPCParser::parseChannelVerify, 4, 4 }, { "connect", &RPCParser::parseConnect, 1, 2 }, { "consensus_info", &RPCParser::parseAsIs, 0, 0 }, diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index c8e2814558..10fdc87231 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -131,7 +131,8 @@ enum error_code_i rpcINTERNAL = 73, // Generic internal error. rpcNOT_IMPL = 74, rpcNOT_SUPPORTED = 75, - rpcLAST = rpcNOT_SUPPORTED // rpcLAST should always equal the last code. + rpcBAD_KEY_TYPE = 76, + rpcLAST = rpcBAD_KEY_TYPE // rpcLAST should always equal the last code. }; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index 10aa65c818..7d8ed8967c 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -39,6 +39,7 @@ constexpr static ErrorInfo unorderedErrorInfos[] {rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."}, {rpcAMENDMENT_BLOCKED, "amendmentBlocked", "Amendment blocked, need upgrade."}, {rpcATX_DEPRECATED, "deprecated", "Use the new API or specify a ledger range."}, + {rpcBAD_KEY_TYPE, "badKeyType", "Bad key type."}, {rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid."}, {rpcBAD_ISSUER, "badIssuer", "Issuer account malformed."}, {rpcBAD_MARKET, "badMarket", "No such market."}, diff --git a/src/ripple/rpc/handlers/PayChanClaim.cpp b/src/ripple/rpc/handlers/PayChanClaim.cpp index 4b46468aa1..d5ba31b1f0 100644 --- a/src/ripple/rpc/handlers/PayChanClaim.cpp +++ b/src/ripple/rpc/handlers/PayChanClaim.cpp @@ -36,16 +36,23 @@ namespace ripple { // { // secret_key: +// key_type: optional; either ed25519 or secp256k1 (default to secp256k1) // channel_id: 256-bit channel id // drops: 64-bit uint (as string) // } Json::Value doChannelAuthorize (RPC::Context& context) { auto const& params (context.params); - for (auto const& p : {jss::secret, jss::channel_id, jss::amount}) + for (auto const& p : {jss::channel_id, jss::amount}) if (!params.isMember (p)) return RPC::missing_field_error (p); + // Compatibility if a key type isn't specified. If it is, the + // keypairForSignature code will validate parameters and return + // the appropriate error. + if (!params.isMember(jss::key_type) && !params.isMember(jss::secret)) + return RPC::missing_field_error (jss::secret); + Json::Value result; auto const [pk, sk] = RPC::keypairForSignature (params, result); if (RPC::contains_error (result)) diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 65dbfa2642..8842738ae2 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -878,7 +878,8 @@ struct PayChan_test : public beast::unit_test::suite Env env (*this); auto const alice = Account ("alice"); auto const bob = Account ("bob"); - env.fund (XRP (10000), alice, bob); + auto const charlie = Account("charlie", KeyType::ed25519); + env.fund (XRP (10000), alice, bob, charlie); auto const pk = alice.pk (); auto const settleDelay = 3600s; auto const channelFunds = XRP (1000); @@ -1036,6 +1037,63 @@ struct PayChan_test : public beast::unit_test::suite !rv[jss::result][jss::signature_verified].asBool()); } } + { + // Try to explicitly specify secp256k1 and Ed25519 keys: + env (create (charlie, alice, channelFunds, settleDelay, charlie.pk())); + env.close(); + + auto const chan = to_string (channel (*env.current (), charlie, alice)); + + std::string cpk; + { + auto const r = + env.rpc ("account_channels", charlie.human (), alice.human ()); + BEAST_EXPECT (r[jss::result][jss::channels].size () == 1); + BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan); + BEAST_EXPECT (r[jss::result][jss::validated]); + cpk = r[jss::result][jss::channels][0u][jss::public_key].asString(); + } + + // Try to authorize without specifying a key type, expect an error: + auto const rs = + env.rpc ("channel_authorize", "charlie", chan, "1000"); + auto const sig = rs[jss::result][jss::signature].asString (); + BEAST_EXPECT (!sig.empty ()); + { + auto const rv = env.rpc( + "channel_verify", cpk, chan, "1000", sig); + BEAST_EXPECT(!rv[jss::result][jss::signature_verified].asBool()); + } + + // Try to authorize using an unknown key type, except an error: + auto const rs1 = + env.rpc ("channel_authorize", "charlie", "nyx", chan, "1000"); + BEAST_EXPECT(rs1[jss::error] == "badKeyType"); + + // Try to authorize using secp256k1; the authorization _should_ + // succeed but the verification should fail: + auto const rs2 = + env.rpc ("channel_authorize", "charlie", "secp256k1", chan, "1000"); + auto const sig2 = rs2[jss::result][jss::signature].asString (); + BEAST_EXPECT (!sig2.empty ()); + { + auto const rv = env.rpc( + "channel_verify", cpk, chan, "1000", sig2); + BEAST_EXPECT(!rv[jss::result][jss::signature_verified].asBool()); + } + + // Try to authorize using Ed25519; expect success: + auto const rs3 = + env.rpc ("channel_authorize", "charlie", "ed25519", chan, "1000"); + auto const sig3 = rs3[jss::result][jss::signature].asString (); + BEAST_EXPECT (!sig3.empty ()); + { + auto const rv = env.rpc( + "channel_verify", cpk, chan, "1000", sig3); + BEAST_EXPECT(rv[jss::result][jss::signature_verified].asBool()); + } + } + { // send malformed amounts rpc requests auto rs = env.rpc("channel_authorize", "alice", chan1Str, "1000x"); @@ -1044,6 +1102,81 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); rs = env.rpc("channel_authorize", "alice", chan1Str, "x"); BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); + { + // Missing channel_id + Json::Value args {Json::objectValue}; + args[jss::amount] = "2000"; + args[jss::key_type] = "secp256k1"; + args[jss::passphrase] = "passphrase_can_be_anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "invalidParams"); + } + { + // Missing amount + Json::Value args {Json::objectValue}; + args[jss::channel_id] = chan1Str; + args[jss::key_type] = "secp256k1"; + args[jss::passphrase] = "passphrase_can_be_anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "invalidParams"); + } + { + // Missing key_type and no secret. + Json::Value args {Json::objectValue}; + args[jss::amount] = "2000"; + args[jss::channel_id] = chan1Str; + args[jss::passphrase] = "passphrase_can_be_anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "invalidParams"); + } + { + // Both passphrase and seed specified. + Json::Value args {Json::objectValue}; + args[jss::amount] = "2000"; + args[jss::channel_id] = chan1Str; + args[jss::key_type] = "secp256k1"; + args[jss::passphrase] = "passphrase_can_be_anything"; + args[jss::seed] = "seed can be anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "invalidParams"); + } + { + // channel_id is not exact hex. + Json::Value args {Json::objectValue}; + args[jss::amount] = "2000"; + args[jss::channel_id] = chan1Str + "1"; + args[jss::key_type] = "secp256k1"; + args[jss::passphrase] = "passphrase_can_be_anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "channelMalformed"); + } + { + //amount is not a string + Json::Value args {Json::objectValue}; + args[jss::amount] = 2000; + args[jss::channel_id] = chan1Str; + args[jss::key_type] = "secp256k1"; + args[jss::passphrase] = "passphrase_can_be_anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); + } + { + // Amount is not a decimal string. + Json::Value args {Json::objectValue}; + args[jss::amount] = "TwoThousand"; + args[jss::channel_id] = chan1Str; + args[jss::key_type] = "secp256k1"; + args[jss::passphrase] = "passphrase_can_be_anything"; + rs = env.rpc ("json", + "channel_authorize", args.toStyledString())[jss::result]; + BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); + } } } diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index 6f7016ff0d..05c9a23bb8 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -2222,10 +2222,11 @@ static RPCCallTestData const rpcCallTestArray [] = "channel_authorize: too many arguments.", __LINE__, { "channel_authorize", - "secret_can_be_anything", + "secp256k1", "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", "2000", - "whatever" + "whatever", + "whenever" }, RPCCallTestData::no_exception, R"({ @@ -2239,6 +2240,27 @@ static RPCCallTestData const rpcCallTestArray [] = ] })" }, +{ + "channel_authorize: bad key type.", __LINE__, + { + "channel_authorize", + "secp257k1", + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", + "2000", + "whatever" + }, + RPCCallTestData::no_exception, + R"({ + "method" : "channel_authorize", + "params" : [ + { + "error" : "badKeyType", + "error_code" : 1, + "error_message" : "Bad key type." + } + ] + })" +}, { "channel_authorize: channel_id too short.", __LINE__, {