mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-03 17:35:51 +00:00
Support Ed25519 keys and signatures:
Recognize a new JSON parameter `key_type` in handlers for wallet_propose
and sign/submit. In addition to letting the caller to specify either of
secp256k1 or ed25519, its presence prohibits the (now-deprecated) use of
heuristically polymorphic parameters for secret data -- the `passphrase`
parameter to wallet_propose will be not be considered as an encoded seed
value (for which `seed` and `seed_hex` should be used), and the `secret`
parameter to sign and submit will be obsoleted entirely by the same trio
above.
* Use constants instead of literals for JSON parameter names.
* Move KeyType to its own unit and add string conversions.
* RippleAddress
* Pass the entire message, rather than a hash, to accountPrivateSign()
and accountPublicVerify().
* Recognize a 33-byte value beginning with 0xED as an Ed25519 key when
signing and verifying (for accounts only).
* Add keyFromSeed() to generate an Ed25519 secret key from a seed.
* Add getSeedFromRPC() to extract the seed from JSON parameters for an
RPC call.
* Add generateKeysFromSeed() to produce a key pair of either type from
a seed.
* Extend Ledger tests to cover both key types.
This commit is contained in:
@@ -2213,6 +2213,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\crypto\impl\KeyType.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\crypto\impl\openssl.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -2227,6 +2231,8 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\crypto\KeyType.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\crypto\RandomNumbers.h">
|
||||
</ClInclude>
|
||||
<None Include="..\..\src\ripple\crypto\README.md">
|
||||
|
||||
@@ -2970,6 +2970,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\crypto\impl\GenerateDeterministicKey.cpp">
|
||||
<Filter>ripple\crypto\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\crypto\impl\KeyType.cpp">
|
||||
<Filter>ripple\crypto\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\crypto\impl\openssl.cpp">
|
||||
<Filter>ripple\crypto\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -2982,6 +2985,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\crypto\impl\RFC1751.cpp">
|
||||
<Filter>ripple\crypto\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\crypto\KeyType.h">
|
||||
<Filter>ripple\crypto</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\crypto\RandomNumbers.h">
|
||||
<Filter>ripple\crypto</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -24,24 +24,24 @@ namespace test {
|
||||
|
||||
class Ledger_test : public beast::unit_test::suite
|
||||
{
|
||||
void test_genesisLedger (bool sign)
|
||||
void test_genesisLedger (bool sign, KeyType keyType)
|
||||
{
|
||||
std::uint64_t const xrp = std::mega::num;
|
||||
|
||||
auto master = createAccount ("masterpassphrase");
|
||||
auto master = createAccount ("masterpassphrase", keyType);
|
||||
|
||||
Ledger::pointer LCL = createGenesisLedger(100000*xrp, master);
|
||||
|
||||
Ledger::pointer ledger = std::make_shared<Ledger>(false, *LCL);
|
||||
|
||||
// User accounts
|
||||
auto gw1 = createAccount ("gw1");
|
||||
auto gw1 = createAccount ("gw1", keyType);
|
||||
expect (gw1.pk != master.pk, "gw1.pk != master.pk");
|
||||
expect (gw1.sk != master.sk, "gw1.sk != master.sk");
|
||||
auto gw2 = createAccount ("gw2");
|
||||
auto gw3 = createAccount ("gw3");
|
||||
auto alice = createAccount ("alice");
|
||||
auto mark = createAccount ("mark");
|
||||
auto gw2 = createAccount ("gw2", keyType);
|
||||
auto gw3 = createAccount ("gw3", keyType);
|
||||
auto alice = createAccount ("alice", keyType);
|
||||
auto mark = createAccount ("mark", keyType);
|
||||
|
||||
// Fund gw1, gw2, gw3, alice, mark from master
|
||||
makeAndApplyPayment(master, gw1, 5000 * xrp, ledger, sign);
|
||||
@@ -90,17 +90,17 @@ class Ledger_test : public beast::unit_test::suite
|
||||
pass ();
|
||||
}
|
||||
|
||||
void test_unsigned_fails ()
|
||||
void test_unsigned_fails (KeyType keyType)
|
||||
{
|
||||
std::uint64_t const xrp = std::mega::num;
|
||||
|
||||
auto master = createAccount ("masterpassphrase");
|
||||
auto master = createAccount ("masterpassphrase", keyType);
|
||||
|
||||
Ledger::pointer LCL = createGenesisLedger (100000 * xrp, master);
|
||||
|
||||
Ledger::pointer ledger = std::make_shared<Ledger> (false, *LCL);
|
||||
|
||||
auto gw1 = createAccount ("gw1");
|
||||
auto gw1 = createAccount ("gw1", keyType);
|
||||
|
||||
auto tx = getPaymentTx(master, gw1, 5000 * xrp, false);
|
||||
|
||||
@@ -129,16 +129,15 @@ class Ledger_test : public beast::unit_test::suite
|
||||
public:
|
||||
void run ()
|
||||
{
|
||||
testcase ("genesisLedger signed transactions");
|
||||
test_genesisLedger (true);
|
||||
test_genesisLedger (true, KeyType::secp256k1);
|
||||
test_genesisLedger (true, KeyType::ed25519);
|
||||
|
||||
testcase ("genesisLedger unsigned transactions");
|
||||
test_genesisLedger (false);
|
||||
test_genesisLedger (false, KeyType::secp256k1);
|
||||
test_genesisLedger (false, KeyType::ed25519);
|
||||
|
||||
testcase ("unsigned invalid");
|
||||
test_unsigned_fails ();
|
||||
test_unsigned_fails (KeyType::secp256k1);
|
||||
test_unsigned_fails (KeyType::ed25519);
|
||||
|
||||
testcase ("getQuality");
|
||||
test_getQuality ();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/tests/common_ledger.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -90,16 +91,16 @@ createGenesisLedger(std::uint64_t start_amount_drops, TestAccount const& master)
|
||||
// Create an account represented by public RippleAddress and private
|
||||
// RippleAddress
|
||||
TestAccount
|
||||
createAccount(std::string const& passphrase)
|
||||
createAccount(std::string const& passphrase, KeyType keyType)
|
||||
{
|
||||
RippleAddress const seed
|
||||
= RippleAddress::createSeedGeneric(passphrase);
|
||||
RippleAddress const generator
|
||||
= RippleAddress::createGeneratorPublic(seed);
|
||||
|
||||
auto keyPair = generateKeysFromSeed(keyType, seed);
|
||||
|
||||
return {
|
||||
std::move(RippleAddress::createAccountPublic(generator, 0)),
|
||||
std::move(RippleAddress::createAccountPrivate(generator, seed, 0)),
|
||||
std::move(keyPair.publicKey),
|
||||
std::move(keyPair.secretKey),
|
||||
std::uint64_t(0)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
#include <ripple/app/transactors/Transactor.h>
|
||||
#include <ripple/basics/seconds_clock.h>
|
||||
#include <ripple/crypto/KeyType.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
@@ -75,7 +76,7 @@ createGenesisLedger(std::uint64_t start_amount_drops, TestAccount const& master)
|
||||
// Create an account represented by public RippleAddress and private
|
||||
// RippleAddress
|
||||
TestAccount
|
||||
createAccount(std::string const& passphrase);
|
||||
createAccount(std::string const& passphrase, KeyType keyType);
|
||||
|
||||
void
|
||||
freezeAccount(TestAccount& account, Ledger::pointer const& ledger, bool sign = true);
|
||||
|
||||
42
src/ripple/crypto/KeyType.h
Normal file
42
src/ripple/crypto/KeyType.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2015 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_CRYPTO_KEYTYPE_H_INCLUDED
|
||||
#define RIPPLE_CRYPTO_KEYTYPE_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum class KeyType
|
||||
{
|
||||
invalid = -1,
|
||||
unknown = invalid,
|
||||
|
||||
secp256k1 = 0,
|
||||
ed25519 = 1,
|
||||
};
|
||||
|
||||
KeyType keyTypeFromString (std::string const& s);
|
||||
|
||||
const char* to_string (KeyType type);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
40
src/ripple/crypto/impl/KeyType.cpp
Normal file
40
src/ripple/crypto/impl/KeyType.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2015 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 <BeastConfig.h>
|
||||
#include <ripple/crypto/KeyType.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
KeyType keyTypeFromString (std::string const& s)
|
||||
{
|
||||
if (s == "secp256k1") return KeyType::secp256k1;
|
||||
if (s == "ed25519" ) return KeyType::ed25519;
|
||||
|
||||
return KeyType::invalid;
|
||||
}
|
||||
|
||||
const char* to_string (KeyType type)
|
||||
{
|
||||
return type == KeyType::secp256k1 ? "secp256k1"
|
||||
: type == KeyType::ed25519 ? "ed25519"
|
||||
: "INVALID";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_PROTOCOL_ANYPUBLICKEY_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/Buffer.h>
|
||||
#include <ripple/crypto/KeyType.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/STExchange.h>
|
||||
#include <ripple/protocol/STObject.h>
|
||||
@@ -34,15 +35,6 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum class KeyType
|
||||
{
|
||||
unknown,
|
||||
secp256k1,
|
||||
ed25519
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Variant container for all public keys. */
|
||||
class AnyPublicKeySlice
|
||||
: public Slice
|
||||
|
||||
@@ -177,6 +177,7 @@ JSS ( issuer ); // in: RipplePathFind, Subscribe,
|
||||
// Unsubscribe, BookOffers
|
||||
// out: paths/Node, STPathSet, STAmount
|
||||
JSS ( key ); // out: WalletSeed
|
||||
JSS ( key_type ); // in/out: WalletPropose, TransactionSign
|
||||
JSS ( last ); // out: RPCVersion
|
||||
JSS ( last_close ); // out: NetworkOPs
|
||||
JSS ( ledger ); // in: NetworkOPs, LedgerCleaner,
|
||||
@@ -296,6 +297,7 @@ JSS ( search_depth ); // in: RipplePathFind
|
||||
JSS ( secret ); // in: TransactionSign, WalletSeed,
|
||||
// ValidationCreate, ValidationSeed
|
||||
JSS ( seed ); // in: WalletAccounts, out: WalletSeed
|
||||
JSS ( seed_hex ); // in: WalletPropose, TransactionSign
|
||||
JSS ( send_currencies ); // out: AccountCurrencies
|
||||
JSS ( seq ); // in: LedgerEntry;
|
||||
// out: NetworkOPs, RPCSub, AccountOffers
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
#ifndef RIPPLE_PROTOCOL_RIPPLEADDRESS_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_RIPPLEADDRESS_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/crypto/Base58Data.h>
|
||||
#include <ripple/crypto/ECDSACanonical.h>
|
||||
#include <ripple/crypto/KeyType.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/protocol/RipplePublicKey.h>
|
||||
#include <ripple/protocol/UInt160.h>
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
@@ -142,7 +145,7 @@ public:
|
||||
void setAccountPublic (Blob const& vPublic);
|
||||
void setAccountPublic (RippleAddress const& generator, int seq);
|
||||
|
||||
bool accountPublicVerify (uint256 const& uHash, Blob const& vucSig,
|
||||
bool accountPublicVerify (Blob const& message, Blob const& vucSig,
|
||||
ECDSA mustBeFullyCanonical) const;
|
||||
|
||||
static RippleAddress createAccountPublic (Blob const& vPublic)
|
||||
@@ -172,7 +175,7 @@ public:
|
||||
void setAccountPrivate (RippleAddress const& naGenerator,
|
||||
RippleAddress const& naSeed, int seq);
|
||||
|
||||
bool accountPrivateSign (uint256 const& uHash, Blob& vucSig) const;
|
||||
Blob accountPrivateSign (Blob const& message) const;
|
||||
|
||||
// Encrypt a message.
|
||||
Blob accountPrivateEncrypt (
|
||||
@@ -285,6 +288,18 @@ operator>=(RippleAddress const& lhs, RippleAddress const& rhs)
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
struct KeyPair
|
||||
{
|
||||
RippleAddress secretKey;
|
||||
RippleAddress publicKey;
|
||||
};
|
||||
|
||||
uint256 keyFromSeed (uint128 const& seed);
|
||||
|
||||
RippleAddress getSeedFromRPC (Json::Value const& params);
|
||||
|
||||
KeyPair generateKeysFromSeed (KeyType keyType, RippleAddress const& seed);
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -25,16 +25,40 @@
|
||||
#include <ripple/crypto/GenerateDeterministicKey.h>
|
||||
#include <ripple/crypto/RandomNumbers.h>
|
||||
#include <ripple/crypto/RFC1751.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/RipplePublicKey.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
#include <ed25519-donna/ed25519.h>
|
||||
#include <openssl/ripemd.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
static
|
||||
bool isCanonicalEd25519Signature (std::uint8_t const* signature)
|
||||
{
|
||||
using std::uint8_t;
|
||||
|
||||
// Big-endian `l`, the Ed25519 subgroup order
|
||||
char const* const order = "\x10\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x14\xDE\xF9\xDE\xA2\xF7\x9C\xD6"
|
||||
"\x58\x12\x63\x1A\x5C\xF5\xD3\xED";
|
||||
|
||||
uint8_t const* const l = reinterpret_cast<uint8_t const*> (order);
|
||||
|
||||
// Take the second half of signature and byte-reverse it to big-endian.
|
||||
uint8_t const* S_le = signature + 32;
|
||||
uint8_t S[32];
|
||||
std::reverse_copy (S_le, S_le + 32, S);
|
||||
|
||||
return std::lexicographical_compare (S, S + 32, l, l + 32);
|
||||
}
|
||||
|
||||
// <-- seed
|
||||
static uint128 PassPhraseToKey (std::string const& passPhrase)
|
||||
{
|
||||
@@ -486,8 +510,28 @@ void RippleAddress::setAccountPublic (RippleAddress const& generator, int seq)
|
||||
}
|
||||
|
||||
bool RippleAddress::accountPublicVerify (
|
||||
uint256 const& uHash, Blob const& vucSig, ECDSA fullyCanonical) const
|
||||
Blob const& message, Blob const& vucSig, ECDSA fullyCanonical) const
|
||||
{
|
||||
if (vchData.size() == 33 && vchData[0] == 0xED)
|
||||
{
|
||||
if (vucSig.size() != 64)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t const* publicKey = &vchData[1];
|
||||
uint8_t const* signature = &vucSig[0];
|
||||
|
||||
if (ed25519_sign_open (message.data(), message.size(), publicKey, signature) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return isCanonicalEd25519Signature (signature);
|
||||
}
|
||||
|
||||
uint256 const uHash = getSHA512Half (message);
|
||||
|
||||
return verifySignature (getAccountPublic(), uHash, vucSig, fullyCanonical);
|
||||
}
|
||||
|
||||
@@ -521,7 +565,7 @@ uint256 RippleAddress::getAccountPrivate () const
|
||||
throw std::runtime_error ("unset source - getAccountPrivate");
|
||||
|
||||
case VER_ACCOUNT_PRIVATE:
|
||||
return uint256 (vchData);
|
||||
return uint256::fromVoid (vchData.data() + (vchData.size() - 32));
|
||||
|
||||
default:
|
||||
throw std::runtime_error ("bad source: " + std::to_string(nVersion));
|
||||
@@ -556,15 +600,32 @@ void RippleAddress::setAccountPrivate (
|
||||
setAccountPrivate (secretKey);
|
||||
}
|
||||
|
||||
bool RippleAddress::accountPrivateSign (uint256 const& uHash, Blob& vucSig) const
|
||||
Blob RippleAddress::accountPrivateSign (Blob const& message) const
|
||||
{
|
||||
vucSig = ECDSASign (uHash, getAccountPrivate());
|
||||
const bool ok = !vucSig.empty();
|
||||
if (vchData.size() == 33 && vchData[0] == 0xED)
|
||||
{
|
||||
uint8_t const* secretKey = &vchData[1];
|
||||
ed25519_public_key publicKey;
|
||||
Blob signature (sizeof (ed25519_signature));
|
||||
|
||||
ed25519_publickey (secretKey, publicKey);
|
||||
|
||||
ed25519_sign (message.data(), message.size(), secretKey, publicKey, &signature[0]);
|
||||
|
||||
assert (isCanonicalEd25519Signature (signature.data()));
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
uint256 const uHash = getSHA512Half (message);
|
||||
|
||||
Blob result = ECDSASign (uHash, getAccountPrivate());
|
||||
bool const ok = !result.empty();
|
||||
|
||||
CondLog (!ok, lsWARNING, RippleAddress)
|
||||
<< "accountPrivateSign: Signing failed.";
|
||||
|
||||
return ok;
|
||||
return result;
|
||||
}
|
||||
|
||||
Blob RippleAddress::accountPrivateEncrypt (
|
||||
@@ -813,4 +874,104 @@ RippleAddress RippleAddress::createSeedGeneric (std::string const& strText)
|
||||
return naNew;
|
||||
}
|
||||
|
||||
uint256 keyFromSeed (uint128 const& seed)
|
||||
{
|
||||
Serializer s;
|
||||
|
||||
s.add128 (seed);
|
||||
uint256 result = s.getSHA512Half();
|
||||
|
||||
s.secureErase ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
RippleAddress getSeedFromRPC (Json::Value const& params)
|
||||
{
|
||||
// This function is only called when `key_type` is present.
|
||||
assert (params.isMember (jss::key_type));
|
||||
|
||||
bool const has_passphrase = params.isMember (jss::passphrase);
|
||||
bool const has_seed = params.isMember (jss::seed);
|
||||
bool const has_seed_hex = params.isMember (jss::seed_hex);
|
||||
|
||||
int const n_secrets = has_passphrase + has_seed + has_seed_hex;
|
||||
|
||||
if (n_secrets > 1)
|
||||
{
|
||||
// `passphrase`, `seed`, and `seed_hex` are mutually exclusive.
|
||||
return RippleAddress();
|
||||
}
|
||||
|
||||
RippleAddress result;
|
||||
|
||||
if (has_seed)
|
||||
{
|
||||
std::string const seed = params[jss::seed].asString();
|
||||
|
||||
result.setSeed (seed);
|
||||
}
|
||||
else if (has_seed_hex)
|
||||
{
|
||||
uint128 seed;
|
||||
std::string const seed_hex = params[jss::seed_hex].asString();
|
||||
|
||||
if (seed_hex.size() != 32 || !seed.SetHex (seed_hex, true))
|
||||
{
|
||||
return RippleAddress();
|
||||
}
|
||||
|
||||
result.setSeed (seed);
|
||||
}
|
||||
else if (has_passphrase)
|
||||
{
|
||||
std::string const passphrase = params[jss::passphrase].asString();
|
||||
|
||||
// Given `key_type`, `passphrase` is always the passphrase.
|
||||
uint128 const seed = PassPhraseToKey (passphrase);
|
||||
result.setSeed (seed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
KeyPair generateKeysFromSeed (KeyType type, RippleAddress const& seed)
|
||||
{
|
||||
KeyPair result;
|
||||
|
||||
if (! seed.isSet())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (type == KeyType::secp256k1)
|
||||
{
|
||||
RippleAddress generator = RippleAddress::createGeneratorPublic (seed);
|
||||
result.secretKey.setAccountPrivate (generator, seed, 0);
|
||||
result.publicKey.setAccountPublic (generator, 0);
|
||||
}
|
||||
else if (type == KeyType::ed25519)
|
||||
{
|
||||
uint256 secretkey = keyFromSeed (seed.getSeed());
|
||||
|
||||
Blob ed25519_key (33);
|
||||
ed25519_key[0] = 0xED;
|
||||
|
||||
assert (secretkey.size() + 1 == ed25519_key.size());
|
||||
memcpy (&ed25519_key[1], secretkey.data(), secretkey.size());
|
||||
result.secretKey.setAccountPrivate (ed25519_key);
|
||||
|
||||
ed25519_publickey (secretkey.data(), &ed25519_key[1]);
|
||||
result.publicKey.setAccountPublic (ed25519_key);
|
||||
|
||||
secretkey.zero(); // security erase
|
||||
}
|
||||
else
|
||||
{
|
||||
assert (false); // not reached
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -152,6 +152,14 @@ STTx::getMentionedAccounts () const
|
||||
return accounts;
|
||||
}
|
||||
|
||||
static Blob getSigningData (STTx const& that)
|
||||
{
|
||||
Serializer s;
|
||||
s.add32 (HashPrefix::txSign);
|
||||
that.add (s, false);
|
||||
return s.getData();
|
||||
}
|
||||
|
||||
uint256
|
||||
STTx::getSigningHash () const
|
||||
{
|
||||
@@ -178,8 +186,7 @@ Blob STTx::getSignature () const
|
||||
|
||||
void STTx::sign (RippleAddress const& private_key)
|
||||
{
|
||||
Blob signature;
|
||||
private_key.accountPrivateSign (getSigningHash (), signature);
|
||||
Blob const signature = private_key.accountPrivateSign (getSigningData (*this));
|
||||
setFieldVL (sfTxnSignature, signature);
|
||||
}
|
||||
|
||||
@@ -196,7 +203,7 @@ bool STTx::checkSign () const
|
||||
RippleAddress n;
|
||||
n.setAccountPublic (getFieldVL (sfSigningPubKey));
|
||||
|
||||
sig_state_ = n.accountPublicVerify (getSigningHash (),
|
||||
sig_state_ = n.accountPublicVerify (getSigningData (*this),
|
||||
getFieldVL (sfTxnSignature), fullyCanonical);
|
||||
}
|
||||
catch (...)
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/basics/TestSuite.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
#include <ripple/protocol/RipplePublicKey.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class RippleAddress_test : public beast::unit_test::suite
|
||||
class RippleAddress_test : public ripple::TestSuite
|
||||
{
|
||||
public:
|
||||
void run()
|
||||
@@ -57,6 +57,16 @@ public:
|
||||
|
||||
expect (generator.humanGenerator () == "fhuJKrhSDzV2SkjLn9qbwm5AaRmrxDPfFsHDCP6yfDZWcxDFz4mt", generator.humanGenerator ());
|
||||
|
||||
// Create ed25519 account public/private key pair.
|
||||
KeyPair keys = generateKeysFromSeed (KeyType::ed25519, naSeed);
|
||||
expectEquals (keys.publicKey.humanAccountPublic(), "aKGheSBjmCsKJVuLNKRAKpZXT6wpk2FCuEZAXJupXgdAxX5THCqR");
|
||||
|
||||
// Check ed25519 account signing.
|
||||
vucTextSig = keys.secretKey.accountPrivateSign (vucTextSrc);
|
||||
|
||||
expect (!vucTextSig.empty(), "ed25519 signing failed.");
|
||||
expect (keys.publicKey.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA()), "ed25519 verify failed.");
|
||||
|
||||
// Create account #0 public/private key pair.
|
||||
RippleAddress naAccountPublic0 = RippleAddress::createAccountPublic (generator, 0);
|
||||
RippleAddress naAccountPrivate0 = RippleAddress::createAccountPrivate (generator, naSeed, 0);
|
||||
@@ -72,15 +82,19 @@ public:
|
||||
expect (naAccountPublic1.humanAccountPublic () == "aBPXpTfuLy1Bhk3HnGTTAqnovpKWQ23NpFMNkAF6F1Atg5vDyPrw", naAccountPublic1.humanAccountPublic ());
|
||||
|
||||
// Check account signing.
|
||||
expect (naAccountPrivate0.accountPrivateSign (uHash, vucTextSig), "Signing failed.");
|
||||
expect (naAccountPublic0.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Verify failed.");
|
||||
expect (!naAccountPublic1.accountPublicVerify (uHash, vucTextSig, ECDSA::not_strict), "Anti-verify failed.");
|
||||
expect (!naAccountPublic1.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Anti-verify failed.");
|
||||
vucTextSig = naAccountPrivate0.accountPrivateSign (vucTextSrc);
|
||||
|
||||
expect (naAccountPrivate1.accountPrivateSign (uHash, vucTextSig), "Signing failed.");
|
||||
expect (naAccountPublic1.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Verify failed.");
|
||||
expect (!naAccountPublic0.accountPublicVerify (uHash, vucTextSig, ECDSA::not_strict), "Anti-verify failed.");
|
||||
expect (!naAccountPublic0.accountPublicVerify (uHash, vucTextSig, ECDSA::strict), "Anti-verify failed.");
|
||||
expect (!vucTextSig.empty(), "Signing failed.");
|
||||
expect (naAccountPublic0.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Verify failed.");
|
||||
expect (!naAccountPublic1.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::not_strict), "Anti-verify failed.");
|
||||
expect (!naAccountPublic1.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Anti-verify failed.");
|
||||
|
||||
vucTextSig = naAccountPrivate1.accountPrivateSign (vucTextSrc);
|
||||
|
||||
expect (!vucTextSig.empty(), "Signing failed.");
|
||||
expect (naAccountPublic1.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Verify failed.");
|
||||
expect (!naAccountPublic0.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::not_strict), "Anti-verify failed.");
|
||||
expect (!naAccountPublic0.accountPublicVerify (vucTextSrc, vucTextSig, ECDSA::strict), "Anti-verify failed.");
|
||||
|
||||
// Check account encryption.
|
||||
Blob vucTextCipher
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/crypto/KeyType.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
#include <ripple/rpc/handlers/WalletPropose.h>
|
||||
#include <ed25519-donna/ed25519.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -35,14 +38,58 @@ Json::Value walletPropose (Json::Value const& params)
|
||||
RippleAddress naSeed;
|
||||
RippleAddress naAccount;
|
||||
|
||||
if (!params.isMember (jss::passphrase))
|
||||
naSeed.setSeedRandom ();
|
||||
KeyType type = KeyType::secp256k1;
|
||||
|
||||
else if (!naSeed.setSeedGeneric (params[jss::passphrase].asString ()))
|
||||
bool const has_key_type = params.isMember (jss::key_type);
|
||||
bool const has_passphrase = params.isMember (jss::passphrase);
|
||||
|
||||
if (has_key_type)
|
||||
{
|
||||
// `key_type` must be valid if present.
|
||||
|
||||
type = keyTypeFromString (params[jss::key_type].asString());
|
||||
|
||||
if (type == KeyType::invalid)
|
||||
{
|
||||
return rpcError (rpcBAD_SEED);
|
||||
}
|
||||
|
||||
naSeed = getSeedFromRPC (params);
|
||||
}
|
||||
else if (has_passphrase)
|
||||
{
|
||||
naSeed.setSeedGeneric (params[jss::passphrase].asString());
|
||||
}
|
||||
else
|
||||
{
|
||||
naSeed.setSeedRandom();
|
||||
}
|
||||
|
||||
if (!naSeed.isSet())
|
||||
{
|
||||
return rpcError(rpcBAD_SEED);
|
||||
}
|
||||
|
||||
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSeed);
|
||||
naAccount.setAccountPublic (naGenerator, 0);
|
||||
if (type == KeyType::secp256k1)
|
||||
{
|
||||
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSeed);
|
||||
naAccount.setAccountPublic (naGenerator, 0);
|
||||
}
|
||||
else if (type == KeyType::ed25519)
|
||||
{
|
||||
uint256 secretkey = keyFromSeed (naSeed.getSeed());
|
||||
|
||||
Blob publickey (33);
|
||||
publickey[0] = 0xED;
|
||||
ed25519_publickey (secretkey.data(), &publickey[1]);
|
||||
secretkey.zero(); // security erase
|
||||
|
||||
naAccount.setAccountPublic (publickey);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert (false); // not reached
|
||||
}
|
||||
|
||||
Json::Value obj (Json::objectValue);
|
||||
|
||||
@@ -51,6 +98,7 @@ Json::Value walletPropose (Json::Value const& params)
|
||||
obj[jss::master_key] = naSeed.humanSeed1751();
|
||||
obj[jss::account_id] = naAccount.humanAccountID ();
|
||||
obj[jss::public_key] = naAccount.humanAccountPublic();
|
||||
obj[jss::key_type] = to_string (type);
|
||||
|
||||
auto acct = naAccount.getAccountPublic();
|
||||
obj[jss::public_key_hex] = strHex(acct.begin(), acct.size());
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/protocol/RippleAddress.h>
|
||||
#include <ripple/rpc/impl/KeypairForSignature.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -25,30 +26,60 @@ namespace RPC {
|
||||
|
||||
KeyPair keypairForSignature (Json::Value const& params, Json::Value& error)
|
||||
{
|
||||
if (! params.isMember ("secret"))
|
||||
{
|
||||
error = RPC::missing_field_error ("secret");
|
||||
bool const has_key_type = params.isMember (jss::key_type);
|
||||
bool const has_passphrase = params.isMember (jss::passphrase);
|
||||
bool const has_secret = params.isMember (jss::secret);
|
||||
bool const has_seed = params.isMember (jss::seed);
|
||||
bool const has_seed_hex = params.isMember (jss::seed_hex);
|
||||
|
||||
int const n_secrets = has_passphrase + has_secret + has_seed + has_seed_hex;
|
||||
|
||||
if (n_secrets == 0)
|
||||
{
|
||||
error = RPC::missing_field_error (jss::secret);
|
||||
return KeyPair();
|
||||
}
|
||||
|
||||
if (n_secrets > 1)
|
||||
{
|
||||
// `passphrase`, `secret`, `seed`, and `seed_hex` are mutually exclusive.
|
||||
error = rpcError (rpcBAD_SECRET);
|
||||
return KeyPair();
|
||||
}
|
||||
|
||||
if (has_key_type && has_secret)
|
||||
{
|
||||
// `secret` is deprecated.
|
||||
error = rpcError (rpcBAD_SECRET);
|
||||
return KeyPair();
|
||||
}
|
||||
|
||||
KeyType type = KeyType::secp256k1;
|
||||
|
||||
RippleAddress seed;
|
||||
|
||||
if (! seed.setSeedGeneric (params["secret"].asString ()))
|
||||
if (has_key_type)
|
||||
{
|
||||
// `key_type` must be valid if present.
|
||||
|
||||
type = keyTypeFromString (params[jss::key_type].asString());
|
||||
|
||||
if (type == KeyType::invalid)
|
||||
{
|
||||
error = rpcError (rpcBAD_SEED);
|
||||
return KeyPair();
|
||||
}
|
||||
|
||||
seed = getSeedFromRPC (params);
|
||||
}
|
||||
else
|
||||
if (! seed.setSeedGeneric (params[jss::secret].asString ()))
|
||||
{
|
||||
error = RPC::make_error (rpcBAD_SEED,
|
||||
RPC::invalid_field_message ("secret"));
|
||||
|
||||
return KeyPair();
|
||||
RPC::invalid_field_message (jss::secret));
|
||||
}
|
||||
|
||||
KeyPair result;
|
||||
|
||||
RippleAddress generator = RippleAddress::createGeneratorPublic (seed);
|
||||
result.secretKey.setAccountPrivate (generator, seed, 0);
|
||||
result.publicKey.setAccountPublic (generator, 0);
|
||||
|
||||
return result;
|
||||
return generateKeysFromSeed (type, seed);
|
||||
}
|
||||
|
||||
} // RPC
|
||||
|
||||
@@ -26,12 +26,6 @@
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
|
||||
struct KeyPair
|
||||
{
|
||||
RippleAddress secretKey;
|
||||
RippleAddress publicKey;
|
||||
};
|
||||
|
||||
KeyPair keypairForSignature (Json::Value const& params, Json::Value& error);
|
||||
|
||||
} // RPC
|
||||
|
||||
@@ -56,6 +56,17 @@ static key_strings const secp256k1_strings =
|
||||
"1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2",
|
||||
};
|
||||
|
||||
static key_strings const ed25519_strings =
|
||||
{
|
||||
"r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1",
|
||||
common::master_key,
|
||||
common::master_seed,
|
||||
common::master_seed_hex,
|
||||
"aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7",
|
||||
"ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D",
|
||||
"77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278",
|
||||
};
|
||||
|
||||
class WalletPropose_test : public ripple::TestSuite
|
||||
{
|
||||
public:
|
||||
@@ -65,19 +76,19 @@ public:
|
||||
Json::Value result = walletPropose (params);
|
||||
|
||||
expect (! contains_error (result));
|
||||
expect (result.isMember ("account_id"));
|
||||
expect (result.isMember ("master_key"));
|
||||
expect (result.isMember ("master_seed"));
|
||||
expect (result.isMember ("master_seed_hex"));
|
||||
expect (result.isMember ("public_key"));
|
||||
expect (result.isMember ("public_key_hex"));
|
||||
expect (result.isMember (jss::account_id));
|
||||
expect (result.isMember (jss::master_key));
|
||||
expect (result.isMember (jss::master_seed));
|
||||
expect (result.isMember (jss::master_seed_hex));
|
||||
expect (result.isMember (jss::public_key));
|
||||
expect (result.isMember (jss::public_key_hex));
|
||||
|
||||
std::string seed = result["master_seed"].asString();
|
||||
std::string seed = result[jss::master_seed].asString();
|
||||
|
||||
result = walletPropose (params);
|
||||
|
||||
// We asked for two random seeds, so they shouldn't match.
|
||||
expect (result["master_seed"].asString() != seed, seed);
|
||||
expect (result[jss::master_seed].asString() != seed, seed);
|
||||
}
|
||||
|
||||
void testSecretWallet (Json::Value const& params, key_strings const& s)
|
||||
@@ -85,12 +96,12 @@ public:
|
||||
Json::Value result = walletPropose (params);
|
||||
|
||||
expect (! contains_error (result));
|
||||
expectEquals (result["account_id"], s.account_id);
|
||||
expectEquals (result["master_key"], s.master_key);
|
||||
expectEquals (result["master_seed"], s.master_seed);
|
||||
expectEquals (result["master_seed_hex"], s.master_seed_hex);
|
||||
expectEquals (result["public_key"], s.public_key);
|
||||
expectEquals (result["public_key_hex"], s.public_key_hex);
|
||||
expectEquals (result[jss::account_id], s.account_id);
|
||||
expectEquals (result[jss::master_key], s.master_key);
|
||||
expectEquals (result[jss::master_seed], s.master_seed);
|
||||
expectEquals (result[jss::master_seed_hex], s.master_seed_hex);
|
||||
expectEquals (result[jss::public_key], s.public_key);
|
||||
expectEquals (result[jss::public_key_hex], s.public_key_hex);
|
||||
}
|
||||
|
||||
void testLegacyPassphrase (char const* value)
|
||||
@@ -98,7 +109,7 @@ public:
|
||||
testcase (value);
|
||||
|
||||
Json::Value params;
|
||||
params["passphrase"] = value;
|
||||
params[jss::passphrase] = value;
|
||||
|
||||
testSecretWallet (params, secp256k1_strings);
|
||||
}
|
||||
@@ -111,10 +122,32 @@ public:
|
||||
testLegacyPassphrase (secp256k1_strings.master_seed_hex);
|
||||
}
|
||||
|
||||
void testKeyType (char const* keyType, key_strings const& strings)
|
||||
{
|
||||
testcase (keyType);
|
||||
|
||||
Json::Value params;
|
||||
params[jss::key_type] = keyType;
|
||||
params[jss::passphrase] = common::passphrase;
|
||||
|
||||
testSecretWallet (params, strings);
|
||||
|
||||
params[jss::seed] = strings.master_seed;
|
||||
|
||||
// Secret fields are mutually exclusive.
|
||||
expect (contains_error (walletPropose (params)));
|
||||
|
||||
params.removeMember (jss::passphrase);
|
||||
|
||||
testSecretWallet (params, strings);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
testRandomWallet();
|
||||
testLegacyPassphrase();
|
||||
testKeyType ("secp256k1", secp256k1_strings);
|
||||
testKeyType ("ed25519", ed25519_strings);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -151,7 +184,7 @@ public:
|
||||
testcase (value);
|
||||
|
||||
Json::Value params;
|
||||
params["secret"] = value;
|
||||
params[jss::secret] = value;
|
||||
|
||||
testSecretWallet (params, secp256k1_strings);
|
||||
}
|
||||
@@ -164,10 +197,50 @@ public:
|
||||
testLegacySecret (secp256k1_strings.master_seed_hex);
|
||||
}
|
||||
|
||||
void testInvalidKeyType (char const* keyType)
|
||||
{
|
||||
testcase (keyType);
|
||||
|
||||
Json::Value params;
|
||||
params[jss::key_type] = keyType;
|
||||
params[jss::passphrase] = common::passphrase;
|
||||
|
||||
Json::Value error;
|
||||
keypairForSignature (params, error);
|
||||
|
||||
expect (contains_error (error));
|
||||
}
|
||||
|
||||
void testKeyType (char const* keyType, key_strings const& strings)
|
||||
{
|
||||
testcase (keyType);
|
||||
|
||||
Json::Value params;
|
||||
params[jss::key_type] = keyType;
|
||||
params[jss::passphrase] = common::passphrase;
|
||||
|
||||
testSecretWallet (params, strings);
|
||||
|
||||
params[jss::seed] = strings.master_seed;
|
||||
|
||||
// Secret fields are mutually exclusive.
|
||||
Json::Value error;
|
||||
keypairForSignature (params, error);
|
||||
|
||||
expect (contains_error (error));
|
||||
|
||||
params.removeMember (jss::passphrase);
|
||||
|
||||
testSecretWallet (params, strings);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
testEmpty();
|
||||
testLegacySecret();
|
||||
testInvalidKeyType ("caesarsalad");
|
||||
testKeyType ("secp256k1", secp256k1_strings);
|
||||
testKeyType ("ed25519", ed25519_strings);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <ripple/crypto/impl/ECDSAKey.cpp>
|
||||
#include <ripple/crypto/impl/ECIES.cpp>
|
||||
#include <ripple/crypto/impl/GenerateDeterministicKey.cpp>
|
||||
#include <ripple/crypto/impl/KeyType.cpp>
|
||||
#include <ripple/crypto/impl/openssl.cpp>
|
||||
#include <ripple/crypto/impl/RandomNumbers.cpp>
|
||||
#include <ripple/crypto/impl/RFC1751.cpp>
|
||||
|
||||
Reference in New Issue
Block a user