diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 3e6921ddb..1e3644e3d 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -2213,6 +2213,10 @@
True
True
+
+ True
+ True
+
True
True
@@ -2227,6 +2231,8 @@
True
True
+
+
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index 7fb871e71..84ddda7dd 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -2970,6 +2970,9 @@
ripple\crypto\impl
+
+ ripple\crypto\impl
+
ripple\crypto\impl
@@ -2982,6 +2985,9 @@
ripple\crypto\impl
+
+ ripple\crypto
+
ripple\crypto
diff --git a/src/ripple/app/ledger/tests/Ledger_test.cpp b/src/ripple/app/ledger/tests/Ledger_test.cpp
index 70597aa52..bab7833e1 100644
--- a/src/ripple/app/ledger/tests/Ledger_test.cpp
+++ b/src/ripple/app/ledger/tests/Ledger_test.cpp
@@ -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(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 (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 ();
}
};
diff --git a/src/ripple/app/tests/common_ledger.cpp b/src/ripple/app/tests/common_ledger.cpp
index 870370f34..58559436b 100644
--- a/src/ripple/app/tests/common_ledger.cpp
+++ b/src/ripple/app/tests/common_ledger.cpp
@@ -18,6 +18,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//==============================================================================
#include
+#include
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)
};
}
diff --git a/src/ripple/app/tests/common_ledger.h b/src/ripple/app/tests/common_ledger.h
index 403113069..4b9933b75 100644
--- a/src/ripple/app/tests/common_ledger.h
+++ b/src/ripple/app/tests/common_ledger.h
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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);
diff --git a/src/ripple/crypto/KeyType.h b/src/ripple/crypto/KeyType.h
new file mode 100644
index 000000000..e555e8e5f
--- /dev/null
+++ b/src/ripple/crypto/KeyType.h
@@ -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
+
+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
diff --git a/src/ripple/crypto/impl/KeyType.cpp b/src/ripple/crypto/impl/KeyType.cpp
new file mode 100644
index 000000000..86e90becd
--- /dev/null
+++ b/src/ripple/crypto/impl/KeyType.cpp
@@ -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
+#include
+
+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";
+}
+
+}
diff --git a/src/ripple/protocol/AnyPublicKey.h b/src/ripple/protocol/AnyPublicKey.h
index 059e5ebc6..f909c681e 100644
--- a/src/ripple/protocol/AnyPublicKey.h
+++ b/src/ripple/protocol/AnyPublicKey.h
@@ -21,6 +21,7 @@
#define RIPPLE_PROTOCOL_ANYPUBLICKEY_H_INCLUDED
#include
+#include
#include
#include
#include
@@ -34,15 +35,6 @@
namespace ripple {
-enum class KeyType
-{
- unknown,
- secp256k1,
- ed25519
-};
-
-//------------------------------------------------------------------------------
-
/** Variant container for all public keys. */
class AnyPublicKeySlice
: public Slice
diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h
index fed1d0fa7..db13702f8 100644
--- a/src/ripple/protocol/JsonFields.h
+++ b/src/ripple/protocol/JsonFields.h
@@ -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
diff --git a/src/ripple/protocol/RippleAddress.h b/src/ripple/protocol/RippleAddress.h
index dfb66cbaf..b84e5be22 100644
--- a/src/ripple/protocol/RippleAddress.h
+++ b/src/ripple/protocol/RippleAddress.h
@@ -20,8 +20,11 @@
#ifndef RIPPLE_PROTOCOL_RIPPLEADDRESS_H_INCLUDED
#define RIPPLE_PROTOCOL_RIPPLEADDRESS_H_INCLUDED
+#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -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
diff --git a/src/ripple/protocol/impl/RippleAddress.cpp b/src/ripple/protocol/impl/RippleAddress.cpp
index 7109a59f9..89bf97516 100644
--- a/src/ripple/protocol/impl/RippleAddress.cpp
+++ b/src/ripple/protocol/impl/RippleAddress.cpp
@@ -25,16 +25,40 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
+#include
#include
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 (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
diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp
index b6ea42c36..0f009d342 100644
--- a/src/ripple/protocol/impl/STTx.cpp
+++ b/src/ripple/protocol/impl/STTx.cpp
@@ -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 (...)
diff --git a/src/ripple/protocol/tests/RippleAddress.test.cpp b/src/ripple/protocol/tests/RippleAddress.test.cpp
index fa06fa859..c6bdfcba1 100644
--- a/src/ripple/protocol/tests/RippleAddress.test.cpp
+++ b/src/ripple/protocol/tests/RippleAddress.test.cpp
@@ -18,15 +18,15 @@
//==============================================================================
#include
+#include
#include
#include
#include
#include
-#include
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
diff --git a/src/ripple/rpc/handlers/WalletPropose.cpp b/src/ripple/rpc/handlers/WalletPropose.cpp
index a01fbe7b3..a14fc9895 100644
--- a/src/ripple/rpc/handlers/WalletPropose.cpp
+++ b/src/ripple/rpc/handlers/WalletPropose.cpp
@@ -18,7 +18,10 @@
//==============================================================================
#include
+#include
+#include
#include
+#include
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());
diff --git a/src/ripple/rpc/impl/KeypairForSignature.cpp b/src/ripple/rpc/impl/KeypairForSignature.cpp
index 16b9cd714..4b5e5b87f 100644
--- a/src/ripple/rpc/impl/KeypairForSignature.cpp
+++ b/src/ripple/rpc/impl/KeypairForSignature.cpp
@@ -18,6 +18,7 @@
//==============================================================================
#include
+#include
#include
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
diff --git a/src/ripple/rpc/impl/KeypairForSignature.h b/src/ripple/rpc/impl/KeypairForSignature.h
index 66e84c82f..3eb32eea3 100644
--- a/src/ripple/rpc/impl/KeypairForSignature.h
+++ b/src/ripple/rpc/impl/KeypairForSignature.h
@@ -26,12 +26,6 @@
namespace ripple {
namespace RPC {
-struct KeyPair
-{
- RippleAddress secretKey;
- RippleAddress publicKey;
-};
-
KeyPair keypairForSignature (Json::Value const& params, Json::Value& error);
} // RPC
diff --git a/src/ripple/rpc/tests/KeyGeneration.test.cpp b/src/ripple/rpc/tests/KeyGeneration.test.cpp
index 81e7d1589..7590e027d 100644
--- a/src/ripple/rpc/tests/KeyGeneration.test.cpp
+++ b/src/ripple/rpc/tests/KeyGeneration.test.cpp
@@ -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);
}
};
diff --git a/src/ripple/unity/crypto.cpp b/src/ripple/unity/crypto.cpp
index b5eca920e..7791a83f5 100644
--- a/src/ripple/unity/crypto.cpp
+++ b/src/ripple/unity/crypto.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include