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