diff --git a/donna.h b/donna.h
index 6153bd45d..577a996f0 100644
--- a/donna.h
+++ b/donna.h
@@ -129,6 +129,76 @@ ed25519_sign_open(const byte *message, size_t messageLength, const byte publicKe
int
ed25519_sign_open(std::istream& stream, const byte publicKey[32], const byte signature[64]);
+//**************************** bip32-ed25519 ****************************//
+
+/// \brief Extend the Ed25519 key.
+/// \param secretKey byte array for the extended private key
+/// \param secretKey byte array with the private key (seed)
+/// \return 0 on success, non-0 otherwise
+int bip32_ed25519_extend(byte secretKey[64], const byte seed[32]);
+
+/// \brief Creates a public key from an extended secret key
+/// \param publicKey byte array for the public key
+/// \param secretKey byte array with the extended private key
+/// \return 0 on success, non-0 otherwise
+/// \details ed25519_publickey() generates a public key from an extended
+/// secret key. Internally ed25519_publickey() performs a scalar
+/// multiplication using the secret key and then writes the result to
+/// publicKey.
+int bip32_ed25519_publickey(byte publicKey[32], const byte secretKey[64]);
+
+/// \brief Creates a signature on a message
+/// \param message byte array with the message
+/// \param messageLength size of the message, in bytes
+/// \param publicKey byte array with the public key
+/// \param secretKey byte array with the extended private key
+/// \param signature byte array for the signature
+/// \return 0 on success, non-0 otherwise
+/// \details ed25519_sign() generates a signature on a message using
+/// the public and private keys. The various buffers can be exact
+/// sizes, and do not require extra space like when using the
+/// NaCl library functions.
+/// \details At the moment the hash function for signing is fixed at
+/// SHA512.
+int bip32_ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[64], const byte publicKey[32], byte signature[64]);
+
+/// \brief Creates a signature on a message
+/// \param stream std::istream derived class
+/// \param publicKey byte array with the public key
+/// \param secretKey byte array with the extended private key
+/// \param signature byte array for the signature
+/// \return 0 on success, non-0 otherwise
+/// \details ed25519_sign() generates a signature on a message using
+/// the public and private keys. The various buffers can be exact
+/// sizes, and do not require extra space like when using the
+/// NaCl library functions.
+/// \details This ed25519_sign() overload handles large streams. It
+/// was added for signing and verifying files that are too large
+/// for a memory allocation.
+/// \details At the moment the hash function for signing is fixed at
+/// SHA512.
+int bip32_ed25519_sign(std::istream& stream, const byte secretKey[64], const byte publicKey[32], byte signature[64]);
+
+/// \brief Add the lower bytes of two secret keys as scalar values.
+/// \param secretKey1 byte array with the extended private key
+/// \param secretKey2 byte array with the extended private key
+/// \param res 32 byte array for the result
+/// \details Add the lower 32 bytes of two extended secret keys as two large scalars.
+/// The result is a 32 byte array. This may be used during child key
+/// derivation when the keys are part of BIP32 style wallets.
+/// \details We only need the leftmost 32 bytes of the extended secret key.
+int bip32_ed25519_scalar_add(const byte secretKey1[64], const byte secretKey2[64], byte res[32]);
+
+/// \brief Add two public keys as curve 25519 points.
+/// \param publicKey1 byte array with the first public key to add.
+/// \param publicKey2 byte array with the second public key to add.
+/// \param res byte array with the public key result.
+/// \return A public key that is the result of the summation.
+/// \details Add two public keys as two points on the elliptic curve 25519. This is
+/// useful during child key derivation when the keys are part of BIP32 style
+/// wallets.
+int bip32_ed25519_point_add(const byte publicKey1[32], const byte publicKey2[32], byte res[32]);
+
//****************************** Internal ******************************//
#ifndef CRYPTOPP_DOXYGEN_PROCESSING
diff --git a/donna_32.cpp b/donna_32.cpp
index 6a72beea6..fc1444f6d 100644
--- a/donna_32.cpp
+++ b/donna_32.cpp
@@ -2099,4 +2099,174 @@ ed25519_sign_open(std::istream& stream, const byte publicKey[32], const byte sig
NAMESPACE_END // Donna
NAMESPACE_END // CryptoPP
+//**************************** bip32-ed25519 ****************************//
+
+NAMESPACE_BEGIN(CryptoPP)
+NAMESPACE_BEGIN(Donna)
+
+int
+bip32_ed25519_extend(byte secretKey[64], const byte seed[32])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ ed25519_extsk(secretKey, seed);
+ return 0;
+}
+
+int
+bip32_ed25519_publickey_CXX(byte publicKey[32], const byte secretKey[64])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm a;
+ ALIGN(ALIGN_SPEC) ge25519 A;
+
+ /* A = aB */
+ expand256_modm(a, secretKey, 32);
+ ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a);
+ ge25519_pack(publicKey, &A);
+
+ return 0;
+}
+
+int
+bip32_ed25519_publickey(byte publicKey[32], const byte secretKey[32])
+{
+ return bip32_ed25519_publickey_CXX(publicKey, secretKey);
+}
+
+int
+bip32_ed25519_sign_CXX(std::istream& stream, const byte extsk[64], const byte pk[32], byte RS[64])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm r, S, a;
+ ALIGN(ALIGN_SPEC) ge25519 R;
+ hash_512bits hashr, hram;
+
+ // Unfortunately we need to read the stream twice. The first time calculates
+ // 'r = H(aExt[32..64], m)'. The second time calculates 'S = H(R,A,m)'. There
+ // is a data dependency due to hashing 'RS' with 'R = [r]B' that does not
+ // allow us to read the stream once.
+ std::streampos where = stream.tellg();
+
+ /* r = H(aExt[32..64], m) */
+ SHA512 hash;
+ hash.Update(extsk + 32, 32);
+ UpdateFromStream(hash, stream);
+ hash.Final(hashr);
+ expand256_modm(r, hashr, 64);
+
+ /* R = rB */
+ ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r);
+ ge25519_pack(RS, &R);
+
+ // Reset stream for the second digest
+ stream.clear();
+ stream.seekg(where);
+
+ /* S = H(R,A,m).. */
+ ed25519_hram(hram, RS, pk, stream);
+ expand256_modm(S, hram, 64);
+
+ /* S = H(R,A,m)a */
+ expand256_modm(a, extsk, 32);
+ mul256_modm(S, S, a);
+
+ /* S = (r + H(R,A,m)a) */
+ add256_modm(S, S, r);
+
+ /* S = (r + H(R,A,m)a) mod L */
+ contract256_modm(RS + 32, S);
+
+ return 0;
+}
+
+int
+bip32_ed25519_sign_CXX(const byte *m, size_t mlen, const byte extsk[64], const byte pk[32], byte RS[64])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm r, S, a;
+ ALIGN(ALIGN_SPEC) ge25519 R;
+ hash_512bits hashr, hram;
+
+ /* r = H(aExt[32..64], m) */
+ SHA512 hash;
+ hash.Update(extsk + 32, 32);
+ hash.Update(m, mlen);
+ hash.Final(hashr);
+ expand256_modm(r, hashr, 64);
+
+ /* R = rB */
+ ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r);
+ ge25519_pack(RS, &R);
+
+ /* S = H(R,A,m).. */
+ ed25519_hram(hram, RS, pk, m, mlen);
+ expand256_modm(S, hram, 64);
+
+ /* S = H(R,A,m)a */
+ expand256_modm(a, extsk, 32);
+ mul256_modm(S, S, a);
+
+ /* S = (r + H(R,A,m)a) */
+ add256_modm(S, S, r);
+
+ /* S = (r + H(R,A,m)a) mod L */
+ contract256_modm(RS + 32, S);
+
+ return 0;
+}
+
+int
+bip32_ed25519_sign(std::istream& stream, const byte secretKey[64], const byte publicKey[32],
+ byte signature[64])
+{
+ return bip32_ed25519_sign_CXX(stream, secretKey, publicKey, signature);
+}
+
+int
+bip32_ed25519_sign(const byte* message, size_t messageLength, const byte secretKey[64],
+ const byte publicKey[32], byte signature[64])
+{
+ return bip32_ed25519_sign_CXX(message, messageLength, secretKey, publicKey, signature);
+}
+
+int
+bip32_ed25519_scalar_add(const byte secretKey1[64], const byte secretKey2[64], byte res[32])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm s1, s2;
+ expand256_modm(s1, secretKey1, 32);
+ expand256_modm(s2, secretKey2, 32);
+ add256_modm(s1, s1, s2);
+ contract256_modm(res, s1);
+
+ return 0;
+}
+
+int
+bip32_ed25519_point_add(const byte publicKey1[32], const byte publicKey2[32], byte res[32])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ ALIGN(ALIGN_SPEC) ge25519 R, P, Q;
+
+ if (!ge25519_unpack_negative_vartime(&P, publicKey1))
+ return -1;
+ if (!ge25519_unpack_negative_vartime(&Q, publicKey2))
+ return -1;
+
+ ge25519_add(&R, &P, &Q);
+ ge25519_pack(res, &R);
+
+ res[31] ^= 0x80;
+ return 0;
+}
+
+NAMESPACE_END // Donna
+NAMESPACE_END // CryptoPP
+
#endif // CRYPTOPP_CURVE25519_32BIT
diff --git a/donna_64.cpp b/donna_64.cpp
index 31255384e..bfbc8130c 100644
--- a/donna_64.cpp
+++ b/donna_64.cpp
@@ -1812,4 +1812,169 @@ ed25519_sign_open(const byte *message, size_t messageLength, const byte publicKe
NAMESPACE_END // Donna
NAMESPACE_END // CryptoPP
+//**************************** bip32-ed25519 ****************************//
+
+NAMESPACE_BEGIN(CryptoPP)
+NAMESPACE_BEGIN(Donna)
+
+int
+bip32_ed25519_extend(byte secretKey[64], const byte seed[32])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ ed25519_extsk(secretKey, seed);
+ return 0;
+}
+
+int bip32_ed25519_publickey_CXX(byte publicKey[32], const byte secretKey[64])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm a;
+ ALIGN(ALIGN_SPEC)
+ ge25519 A;
+ hash_512bits extsk;
+
+ /* A = aB */
+ expand256_modm(a, secretKey, 32);
+ ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a);
+ ge25519_pack(publicKey, &A);
+
+ return 0;
+}
+
+int bip32_ed25519_publickey(byte publicKey[32], const byte secretKey[64])
+{
+ return bip32_ed25519_publickey_CXX(publicKey, secretKey);
+}
+
+int bip32_ed25519_sign_CXX(std::istream& stream, const byte extsk[64], const byte pk[32], byte RS[64])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm r, S, a;
+ ALIGN(ALIGN_SPEC) ge25519 R;
+ hash_512bits hashr, hram;
+
+ // Unfortunately we need to read the stream twice. The first time calculates
+ // 'r = H(aExt[32..64], m)'. The second time calculates 'S = H(R,A,m)'. There
+ // is a data dependency due to hashing 'RS' with 'R = [r]B' that does not
+ // allow us to read the stream once.
+ std::streampos where = stream.tellg();
+
+ /* r = H(aExt[32..64], m) */
+ SHA512 hash;
+ hash.Update(extsk + 32, 32);
+ UpdateFromStream(hash, stream);
+ hash.Final(hashr);
+ expand256_modm(r, hashr, 64);
+
+ /* R = rB */
+ ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r);
+ ge25519_pack(RS, &R);
+
+ // Reset stream for the second digest
+ stream.clear();
+ stream.seekg(where);
+
+ /* S = H(R,A,m).. */
+ ed25519_hram(hram, RS, pk, stream);
+ expand256_modm(S, hram, 64);
+
+ /* S = H(R,A,m)a */
+ expand256_modm(a, extsk, 32);
+ mul256_modm(S, S, a);
+
+ /* S = (r + H(R,A,m)a) */
+ add256_modm(S, S, r);
+
+ /* S = (r + H(R,A,m)a) mod L */
+ contract256_modm(RS + 32, S);
+ return 0;
+}
+
+int bip32_ed25519_sign_CXX(const byte* m, size_t mlen, const byte extsk[32], const byte pk[32], byte RS[64])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm r, S, a;
+ ALIGN(ALIGN_SPEC)
+ ge25519 R;
+ hash_512bits hashr, hram;
+
+ /* r = H(aExt[32..64], m) */
+ SHA512 hash;
+ hash.Update(extsk + 32, 32);
+ hash.Update(m, mlen);
+ hash.Final(hashr);
+ expand256_modm(r, hashr, 64);
+
+ /* R = rB */
+ ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r);
+ ge25519_pack(RS, &R);
+
+ /* S = H(R,A,m).. */
+ ed25519_hram(hram, RS, pk, m, mlen);
+ expand256_modm(S, hram, 64);
+
+ /* S = H(R,A,m)a */
+ expand256_modm(a, extsk, 32);
+ mul256_modm(S, S, a);
+
+ /* S = (r + H(R,A,m)a) */
+ add256_modm(S, S, r);
+
+ /* S = (r + H(R,A,m)a) mod L */
+ contract256_modm(RS + 32, S);
+ return 0;
+}
+
+int bip32_ed25519_sign(std::istream& stream, const byte secretKey[64], const byte publicKey[32],
+ byte signature[64])
+{
+ return bip32_ed25519_sign_CXX(stream, secretKey, publicKey, signature);
+}
+
+int bip32_ed25519_sign(const byte *message, size_t messageLength, const byte secretKey[64],
+ const byte publicKey[32], byte signature[64])
+{
+ return bip32_ed25519_sign_CXX(message, messageLength, secretKey, publicKey, signature);
+}
+
+int
+bip32_ed25519_scalar_add(const byte secretKey1[64], const byte secretKey2[64], byte res[32])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ bignum256modm s1, s2;
+ expand256_modm(s1, secretKey1, 32);
+ expand256_modm(s2, secretKey2, 32);
+ add256_modm(s1, s1, s2);
+ contract256_modm(res, s1);
+
+ return 0;
+}
+
+int
+bip32_ed25519_point_add(const byte publicKey1[32], const byte publicKey2[32], byte res[32])
+{
+ using namespace CryptoPP::Donna::Ed25519;
+
+ ALIGN(ALIGN_SPEC) ge25519 R, P, Q;
+
+ if (!ge25519_unpack_negative_vartime(&P, publicKey1))
+ return -1;
+ if (!ge25519_unpack_negative_vartime(&Q, publicKey2))
+ return -1;
+
+ ge25519_add(&R, &P, &Q);
+ ge25519_pack(res, &R);
+
+ res[31] ^= 0x80;
+ return 0;
+}
+
+NAMESPACE_END // Donna
+NAMESPACE_END // CryptoPP
+
#endif // CRYPTOPP_CURVE25519_64BIT
diff --git a/validat3.cpp b/validat3.cpp
index b0d9e6949..248ac9a79 100644
--- a/validat3.cpp
+++ b/validat3.cpp
@@ -90,6 +90,7 @@ bool ValidateAll(bool thorough)
pass=TestEncryptors() && pass;
pass=TestX25519() && pass;
pass=TestEd25519() && pass;
+ pass=TestBIP32Ed25519() && pass;
#endif
pass=ValidateCRC32() && pass;
diff --git a/validat7.cpp b/validat7.cpp
index 62f5b1d08..2b53ac67b 100644
--- a/validat7.cpp
+++ b/validat7.cpp
@@ -701,5 +701,165 @@ bool TestEd25519()
return pass;
}
+// TestBIP32Ed25519 includes validation of the public APIs added to support
+// BIP32-Ed25519 hierarchical-deterministic (HD) wallet keys. The API uses only
+// existing cryptographic methods but uses extended secret keys.
+// TestBIP32Ed25519 called in Debug builds.
+bool TestBIP32Ed25519()
+{
+ std::cout << "\nTesting BIP32-Ed25519 Keys and Signatures...\n\n";
+ bool pass = true, fail;
+
+ size_t i = 0;
+ unsigned int failed = 0;
+
+ const unsigned int SIGN_COUNT = 64, MSG_SIZE=128;
+ const unsigned int NACL_EXTRA=NaCl::crypto_sign_BYTES;
+
+ // Test key conversion
+ byte seed[32], sk1[64], sk2[64], pk1[32], pk2[32];
+ for (i = 0, failed = 0; i