mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 20:25:52 +00:00
Implement paychannel verify/authorize
This commit is contained in:
@@ -76,6 +76,8 @@ target_sources(reporting PRIVATE
|
||||
handlers/AccountCurrencies.cpp
|
||||
handlers/AccountOffers.cpp
|
||||
handlers/AccountObjects.cpp)
|
||||
handlers/ChannelAuthorize.cpp
|
||||
handlers/ChannelVerify.cpp)
|
||||
|
||||
|
||||
message(${Boost_LIBRARIES})
|
||||
|
||||
109
handlers/ChannelAuthorize.cpp
Normal file
109
handlers/ChannelAuthorize.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2014 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
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/PayChan.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <optional>
|
||||
|
||||
void
|
||||
serializePayChanAuthorization(
|
||||
ripple::Serializer& msg,
|
||||
ripple::uint256 const& key,
|
||||
ripple::XRPAmount const& amt)
|
||||
{
|
||||
msg.add32(ripple::HashPrefix::paymentChannelClaim);
|
||||
msg.addBitString(key);
|
||||
msg.add64(amt.drops());
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
doChannelAuthorize(boost::json::object const& request)
|
||||
{
|
||||
boost::json::object response;
|
||||
if(!request.contains("channel_id"))
|
||||
{
|
||||
response["error"] = "missing field channel_id";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.contains("amount"))
|
||||
{
|
||||
response["error"] = "missing field amount";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.contains("key_type") && !request.contains("secret"))
|
||||
{
|
||||
response["error"] = "missing field secret";
|
||||
return response;
|
||||
}
|
||||
|
||||
boost::json::value error = nullptr;
|
||||
auto const [pk, sk] = keypairFromRequst(request, error);
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::uint256 channelId;
|
||||
if (!channelId.parseHex(request.at("channel_id").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "channel id malformed";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.at("amount").is_string())
|
||||
{
|
||||
response["error"] = "channel amount malformed";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto optDrops =
|
||||
ripple::to_uint64(request.at("amount").as_string().c_str());
|
||||
|
||||
if (!optDrops)
|
||||
{
|
||||
response["error"] = "could not parse channel amount";
|
||||
return response;
|
||||
}
|
||||
|
||||
std::uint64_t drops = *optDrops;
|
||||
|
||||
ripple::Serializer msg;
|
||||
ripple::serializePayChanAuthorization(msg, channelId, ripple::XRPAmount(drops));
|
||||
|
||||
try
|
||||
{
|
||||
auto const buf = ripple::sign(pk, sk, msg.slice());
|
||||
response["signature"] = ripple::strHex(buf);
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
response["error"] = "Exception occurred during signing.";
|
||||
return response;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
119
handlers/ChannelVerify.cpp
Normal file
119
handlers/ChannelVerify.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2014 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
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/PayChan.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <optional>
|
||||
|
||||
boost::json::object
|
||||
doChannelVerify(boost::json::object const& request)
|
||||
{
|
||||
boost::json::object response;
|
||||
if(!request.contains("channel_id"))
|
||||
{
|
||||
response["error"] = "missing field channel_id";
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!request.contains("amount"))
|
||||
{
|
||||
response["error"] = "missing field amount";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.contains("signature"))
|
||||
{
|
||||
response["error"] = "missing field signature";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.contains("public_key"))
|
||||
{
|
||||
response["error"] = "missing field public_key";
|
||||
return response;
|
||||
}
|
||||
|
||||
boost::optional<ripple::PublicKey> pk;
|
||||
{
|
||||
std::string const strPk = request.at("public_key").as_string().c_str();
|
||||
pk = ripple::parseBase58<ripple::PublicKey>(ripple::TokenType::AccountPublic, strPk);
|
||||
|
||||
if (!pk)
|
||||
{
|
||||
auto pkHex = ripple::strUnHex(strPk);
|
||||
if (!pkHex)
|
||||
{
|
||||
response["error"] = "malformed public key";
|
||||
return response;
|
||||
}
|
||||
auto const pkType = ripple::publicKeyType(ripple::makeSlice(*pkHex));
|
||||
if (!pkType)
|
||||
{
|
||||
response["error"] = "invalid key type";
|
||||
}
|
||||
|
||||
pk.emplace(ripple::makeSlice(*pkHex));
|
||||
}
|
||||
}
|
||||
|
||||
ripple::uint256 channelId;
|
||||
if (!channelId.parseHex(request.at("channel_id").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "channel id malformed";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto optDrops =
|
||||
ripple::to_uint64(request.at("amount").as_string().c_str());
|
||||
|
||||
if (!optDrops)
|
||||
{
|
||||
response["error"] = "could not parse channel amount";
|
||||
return response;
|
||||
}
|
||||
|
||||
std::uint64_t drops = *optDrops;
|
||||
|
||||
if (!request.at("signature").is_string())
|
||||
{
|
||||
response["error"] = "signature must be type string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto sig = ripple::strUnHex(request.at("signature").as_string().c_str());
|
||||
|
||||
if (!sig || !sig->size())
|
||||
{
|
||||
response["error"] = "Invalid signature";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::Serializer msg;
|
||||
ripple::serializePayChanAuthorization(msg, channelId, ripple::XRPAmount(drops));
|
||||
|
||||
response["signature_verified"] =
|
||||
ripple::verify(*pk, msg.slice(), ripple::makeSlice(*sig), true);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -174,3 +174,163 @@ traverseOwnedNodes(
|
||||
|
||||
return nextCursor;
|
||||
}
|
||||
boost::optional<ripple::Seed>
|
||||
parseRippleLibSeed(boost::json::value const& value)
|
||||
{
|
||||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a
|
||||
// non-standard way. While rippled never encode seeds that way, we
|
||||
// try to detect such keys to avoid user confusion.
|
||||
if (!value.is_string())
|
||||
return {};
|
||||
|
||||
auto const result =
|
||||
ripple::decodeBase58Token(value.as_string().c_str(), ripple::TokenType::None);
|
||||
|
||||
if (result.size() == 18 &&
|
||||
static_cast<std::uint8_t>(result[0]) == std::uint8_t(0xE1) &&
|
||||
static_cast<std::uint8_t>(result[1]) == std::uint8_t(0x4B))
|
||||
return ripple::Seed(ripple::makeSlice(result.substr(2)));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<ripple::PublicKey, ripple::SecretKey>
|
||||
keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
{
|
||||
bool const has_key_type = request.contains("key_type");
|
||||
|
||||
// All of the secret types we allow, but only one at a time.
|
||||
// The array should be constexpr, but that makes Visual Studio unhappy.
|
||||
static std::string const secretTypes[]{
|
||||
"passphrase",
|
||||
"secret",
|
||||
"seed",
|
||||
"seed_hex"};
|
||||
|
||||
// Identify which secret type is in use.
|
||||
std::string secretType = "";
|
||||
int count = 0;
|
||||
for (auto t : secretTypes)
|
||||
{
|
||||
if (request.contains(t))
|
||||
{
|
||||
++count;
|
||||
secretType = t;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
error = "missing field secret";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
error = "Exactly one of the following must be specified: "
|
||||
" passphrase, secret, seed, or seed_hex";
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<ripple::KeyType> keyType;
|
||||
boost::optional<ripple::Seed> seed;
|
||||
|
||||
if (has_key_type)
|
||||
{
|
||||
if (!request.at("key_type").is_string())
|
||||
{
|
||||
error = "key_type must be string";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string key_type = request.at("key_type").as_string().c_str();
|
||||
keyType = ripple::keyTypeFromString(key_type);
|
||||
|
||||
if (!keyType)
|
||||
{
|
||||
error = "Invalid field key_type";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (secretType == "secret")
|
||||
{
|
||||
error = "The secret field is not allowed if key_type is used.";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a
|
||||
// non-standard way. While we never encode seeds that way, we try
|
||||
// to detect such keys to avoid user confusion.
|
||||
if (secretType != "seed_hex")
|
||||
{
|
||||
seed = parseRippleLibSeed(request.at(secretType));
|
||||
|
||||
if (seed)
|
||||
{
|
||||
// If the user passed in an Ed25519 seed but *explicitly*
|
||||
// requested another key type, return an error.
|
||||
if (keyType.value_or(ripple::KeyType::ed25519) != ripple::KeyType::ed25519)
|
||||
{
|
||||
error = "Specified seed is for an Ed25519 wallet.";
|
||||
return {};
|
||||
}
|
||||
|
||||
keyType = ripple::KeyType::ed25519;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyType)
|
||||
keyType = ripple::KeyType::secp256k1;
|
||||
|
||||
if (!seed)
|
||||
{
|
||||
if (has_key_type)
|
||||
{
|
||||
if (!request.at(secretType).is_string())
|
||||
{
|
||||
error = "secret value must be string";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string key = request.at(secretType).as_string().c_str();
|
||||
|
||||
if (secretType == "seed")
|
||||
seed = ripple::parseBase58<ripple::Seed>(key);
|
||||
else if (secretType == "passphrase")
|
||||
seed = ripple::parseGenericSeed(key);
|
||||
else if (secretType == "seed_hex")
|
||||
{
|
||||
ripple::uint128 s;
|
||||
if (s.parseHex(key))
|
||||
seed.emplace(ripple::Slice(s.data(), s.size()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!request.at("secret").is_string())
|
||||
{
|
||||
error = "field secret should be a string";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string secret = request.at("secret").as_string().c_str();
|
||||
seed = ripple::parseGenericSeed(secret);
|
||||
}
|
||||
}
|
||||
|
||||
if (!seed)
|
||||
{
|
||||
error = "Bad Seed: invalid field message secretType";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (keyType != ripple::KeyType::secp256k1
|
||||
&& keyType != ripple::KeyType::ed25519)
|
||||
{
|
||||
error = "keypairForSignature: invalid key type";
|
||||
return {};
|
||||
}
|
||||
|
||||
return generateKeyPair(*keyType, *seed);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <reporting/BackendInterface.h>
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
accountFromStringStrict(std::string const& account);
|
||||
|
||||
@@ -32,5 +33,9 @@ traverseOwnedNodes(
|
||||
std::uint32_t sequence,
|
||||
ripple::uint256 const& cursor,
|
||||
std::function<bool(ripple::SLE)> atOwnedNode);
|
||||
std::pair<ripple::PublicKey, ripple::SecretKey>
|
||||
keypairFromRequst(
|
||||
boost::json::object const& request,
|
||||
boost::json::value& error);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1346,9 +1346,9 @@ CassandraBackend::open(bool readOnly)
|
||||
continue;
|
||||
|
||||
query.str("");
|
||||
query << "CREATE TABLE IF NOT EXISTS " << tablePrefix << "transactions"
|
||||
<< " ( hash blob PRIMARY KEY, ledger_sequence bigint, "
|
||||
"transaction "
|
||||
query
|
||||
<< "CREATE TABLE IF NOT EXISTS " << tablePrefix << "transactions"
|
||||
<< " ( hash blob PRIMARY KEY, ledger_sequence bigint, transaction "
|
||||
"blob, metadata blob)";
|
||||
if (!executeSimpleStatement(query.str()))
|
||||
continue;
|
||||
@@ -1383,7 +1383,6 @@ CassandraBackend::open(bool readOnly)
|
||||
<< " LIMIT 1";
|
||||
if (!executeSimpleStatement(query.str()))
|
||||
continue;
|
||||
|
||||
query.str("");
|
||||
query << "CREATE TABLE IF NOT EXISTS " << tablePrefix << "books"
|
||||
<< " ( book blob, sequence bigint, quality_key tuple<blob, blob>, PRIMARY KEY "
|
||||
|
||||
@@ -49,6 +49,8 @@ enum RPCCommand {
|
||||
account_currencies,
|
||||
account_offers,
|
||||
account_objects
|
||||
channel_authorize,
|
||||
channel_verify
|
||||
};
|
||||
std::unordered_map<std::string, RPCCommand> commandMap{
|
||||
{"tx", tx},
|
||||
@@ -63,7 +65,9 @@ std::unordered_map<std::string, RPCCommand> commandMap{
|
||||
{"account_lines", account_lines},
|
||||
{"account_currencies", account_currencies},
|
||||
{"account_offers", account_offers},
|
||||
{"account_objects", account_objects}};
|
||||
{"account_objects", account_objects},
|
||||
{"channel_authorize", channel_authorize},
|
||||
{"channel_verify", channel_verify}};
|
||||
|
||||
boost::json::object
|
||||
doAccountInfo(
|
||||
@@ -113,6 +117,9 @@ boost::json::object
|
||||
doAccountObjects(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
doChannelAuthorize(boost::json::object const& request);
|
||||
boost::json::object
|
||||
doChannelVerify(boost::json::object const& request);
|
||||
|
||||
boost::json::object
|
||||
buildResponse(
|
||||
@@ -126,42 +133,34 @@ buildResponse(
|
||||
{
|
||||
case tx:
|
||||
return doTx(request, backend);
|
||||
break;
|
||||
case account_tx:
|
||||
return doAccountTx(request, backend);
|
||||
break;
|
||||
case ledger:
|
||||
return doLedger(request, backend);
|
||||
break;
|
||||
case ledger_entry:
|
||||
return doLedgerEntry(request, backend);
|
||||
break;
|
||||
case ledger_range:
|
||||
return doLedgerRange(request, backend);
|
||||
break;
|
||||
case ledger_data:
|
||||
return doLedgerData(request, backend);
|
||||
break;
|
||||
case account_info:
|
||||
return doAccountInfo(request, backend);
|
||||
break;
|
||||
case book_offers:
|
||||
return doBookOffers(request, backend);
|
||||
break;
|
||||
case account_channels:
|
||||
return doAccountChannels(request, backend);
|
||||
break;
|
||||
case account_lines:
|
||||
return doAccountLines(request, backend);
|
||||
break;
|
||||
case account_currencies:
|
||||
return doAccountCurrencies(request, backend);
|
||||
break;
|
||||
case account_offers:
|
||||
return doAccountOffers(request, backend);
|
||||
break;
|
||||
case account_objects:
|
||||
return doAccountObjects(request, backend);
|
||||
case channel_authorize:
|
||||
return doChannelAuthorize(request);
|
||||
case channel_verify:
|
||||
return doChannelVerify(request);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG_TRIVIAL(error) << "Unknown command: " << command;
|
||||
|
||||
Reference in New Issue
Block a user