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:
Josh Juran
2015-02-26 16:20:16 -08:00
parent 1b46e003c3
commit d082a0696d
18 changed files with 527 additions and 94 deletions

View File

@@ -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">

View File

@@ -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>

View File

@@ -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 ();
}
};

View File

@@ -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)
};
}

View File

@@ -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);

View 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

View 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";
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 (...)

View File

@@ -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

View File

@@ -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());

View File

@@ -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

View File

@@ -26,12 +26,6 @@
namespace ripple {
namespace RPC {
struct KeyPair
{
RippleAddress secretKey;
RippleAddress publicKey;
};
KeyPair keypairForSignature (Json::Value const& params, Json::Value& error);
} // RPC

View File

@@ -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);
}
};

View File

@@ -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>