Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/session/ed25519.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ LIBSESSION_EXPORT bool session_ed25519_verify(
const unsigned char* msg,
size_t msg_len);

/// API: crypto/session_ed25519_pro_key_pair_for_ed25519_seed
///
/// Generate the deterministic Master Session Pro key for signing requests to interact with the
/// Session Pro features of the protocol.
///
/// Inputs:
/// - `ed25519_seed` -- [in] the seed to the long-term key for the Session account to derive the
/// deterministic key from.
/// - `ed25519_sk_out` -- [out] pointer to a buffer of 64 bytes where the private key will be
/// written.
///
/// Outputs:
/// - `bool` -- True if the key pair was successfully derived, false if failed.
LIBSESSION_EXPORT bool session_ed25519_pro_key_pair_for_ed25519_seed(
const unsigned char* ed25519_seed, /* 32 bytes */
unsigned char* ed25519_sk_out /*64 byte output buffer*/);

#ifdef __cplusplus
}
#endif
16 changes: 14 additions & 2 deletions include/session/ed25519.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include <span>
#include <vector>

#include "types.hpp"

namespace session::ed25519 {

/// Generates a random Ed25519 key pair
Expand Down Expand Up @@ -58,4 +56,18 @@ bool verify(
std::span<const unsigned char> pubkey,
std::span<const unsigned char> msg);

/// API: ed25519/ed25519_pro_key_pair_for_ed25519_seed
///
/// Generate the deterministic Master Session Pro key for signing requests to interact with the
/// Session Pro features of the protocol.
///
/// Inputs:
/// - `ed25519_seed` -- the seed to the long-term key for the Session account to derive the
/// deterministic key from.
///
/// Outputs:
/// - The libsodium-style Master Session Pro Ed25519 secret key, 64 bytes.
std::array<unsigned char, 64> ed25519_pro_key_pair_for_ed25519_seed(
std::span<const unsigned char> ed25519_seed);

} // namespace session::ed25519
50 changes: 45 additions & 5 deletions src/ed25519.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "session/ed25519.hpp"

#include <sodium/crypto_generichash_blake2b.h>
#include <sodium/crypto_sign.h>
#include <sodium/crypto_sign_ed25519.h>

Expand All @@ -8,13 +9,35 @@
#include "session/export.h"
#include "session/sodium_array.hpp"

namespace session::ed25519 {

template <size_t N>
using cleared_array = sodium_cleared<std::array<unsigned char, N>>;

using uc32 = std::array<unsigned char, 32>;
using cleared_uc64 = cleared_array<64>;
using uc64 = std::array<unsigned char, 64>;

namespace {
uc64 derived_ed25519_keypair(std::span<const unsigned char> ed25519_seed, std::string_view key) {
if (ed25519_seed.size() != 32 && ed25519_seed.size() != 64)
throw std::invalid_argument{
"Invalid ed25519_seed: expected 32 bytes or libsodium style 64 bytes seed"};

// Construct seed for derived key
// new_seed = Blake2b32(ed25519_seed, key=<key>)
// b/B = Ed25519FromSeed(new_seed)
session::cleared_uc32 s2 = {};
int hash_result = crypto_generichash_blake2b(
s2.data(),
s2.size(),
ed25519_seed.data(),
ed25519_seed.size(),
reinterpret_cast<const unsigned char*>(key.data()),
key.size());
assert(hash_result == 0); // This function can't return 0 unless misused

auto [pubkey, privkey] = session::ed25519::ed25519_key_pair(s2);
return privkey;
}
} // namespace

namespace session::ed25519 {

std::pair<std::array<unsigned char, 32>, std::array<unsigned char, 64>> ed25519_key_pair() {
std::array<unsigned char, 32> ed_pk;
Expand Down Expand Up @@ -86,6 +109,11 @@ bool verify(
crypto_sign_ed25519_verify_detached(sig.data(), msg.data(), msg.size(), pubkey.data()));
}

std::array<unsigned char, 64> ed25519_pro_key_pair_for_ed25519_seed(
std::span<const unsigned char> ed25519_seed) {
auto result = derived_ed25519_keypair(ed25519_seed, "SessionProRandom");
return result;
}
} // namespace session::ed25519

using namespace session;
Expand Down Expand Up @@ -157,3 +185,15 @@ LIBSESSION_C_API bool session_ed25519_verify(
std::span<const unsigned char>{pubkey, 32},
std::span<const unsigned char>{msg, msg_len});
}

LIBSESSION_C_API bool session_ed25519_pro_key_pair_for_ed25519_seed(
const unsigned char* ed25519_seed, unsigned char* ed25519_sk_out) {
try {
auto seed = std::span<const unsigned char>(ed25519_seed, 32);
uc64 sk = session::ed25519::ed25519_pro_key_pair_for_ed25519_seed(seed);
std::memcpy(ed25519_sk_out, sk.data(), sk.size());
return true;
} catch (...) {
return false;
}
}
129 changes: 90 additions & 39 deletions tests/test_ed25519.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,135 @@
#include <catch2/catch_test_macros.hpp>
#include <session/util.hpp>

#include "session/ed25519.h"
#include "session/ed25519.hpp"
#include "utils.hpp"

TEST_CASE("Ed25519 key pair generation", "[ed25519][keypair]") {
// Generate two random key pairs and make sure they don't match
auto kp1 = session::ed25519::ed25519_key_pair();
auto kp2 = session::ed25519::ed25519_key_pair();
auto [pk1, sk1] = session::ed25519::ed25519_key_pair();
auto [pk2, sk2] = session::ed25519::ed25519_key_pair();

CHECK(kp1.first.size() == 32);
CHECK(kp1.second.size() == 64);
CHECK(kp1.first != kp2.first);
CHECK(kp1.second != kp2.second);
CHECK(pk1.size() == 32);
CHECK(sk1.size() == 64);
CHECK(pk1 != pk2);
CHECK(sk1 != sk2);
}

TEST_CASE("Ed25519 key pair generation seed", "[ed25519][keypair]") {
using namespace session;

auto ed_seed1 = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes;
auto ed_seed2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes;
auto ed_seed_invalid = "010203040506070809"_hexbytes;
constexpr auto ed_seed1 =
"4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hex_u;
constexpr auto ed_seed2 =
"5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hex_u;
constexpr auto ed_seed_invalid = "010203040506070809"_hex_u;

auto kp1 = session::ed25519::ed25519_key_pair(to_span(ed_seed1));
auto kp2 = session::ed25519::ed25519_key_pair(to_span(ed_seed2));
CHECK_THROWS(session::ed25519::ed25519_key_pair(to_span(ed_seed_invalid)));
auto [pk1, sk1] = session::ed25519::ed25519_key_pair(ed_seed1);
auto [pk2, sk2] = session::ed25519::ed25519_key_pair(ed_seed2);
CHECK_THROWS(session::ed25519::ed25519_key_pair(ed_seed_invalid));

CHECK(kp1.first.size() == 32);
CHECK(kp1.second.size() == 64);
CHECK(kp1.first != kp2.first);
CHECK(kp1.second != kp2.second);
CHECK(oxenc::to_hex(kp1.first.begin(), kp1.first.end()) ==
"8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f");
CHECK(oxenc::to_hex(kp2.first.begin(), kp2.first.end()) ==
"cd83ca3d13ad8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786");
CHECK(pk1.size() == 32);
CHECK(sk1.size() == 64);
CHECK(pk1 != pk2);
CHECK(sk1 != sk2);
CHECK(oxenc::to_hex(pk1) == "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f");
CHECK(oxenc::to_hex(pk2) == "cd83ca3d13ad8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786");

auto kp_sk1 =
"4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a"
"87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f";
auto kp_sk2 =
"5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876cd83ca3d13a"
"d8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786";
CHECK(oxenc::to_hex(kp1.second.begin(), kp1.second.end()) == kp_sk1);
CHECK(oxenc::to_hex(kp2.second.begin(), kp2.second.end()) == kp_sk2);
CHECK(oxenc::to_hex(sk1) == kp_sk1);
CHECK(oxenc::to_hex(sk2) == kp_sk2);
}

TEST_CASE("Ed25519 seed for private key", "[ed25519][seed]") {
using namespace session;

auto ed_sk1 =
constexpr auto ed_sk1 =
"4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a"
"87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes;
auto ed_sk2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes;
auto ed_sk_invalid = "010203040506070809"_hexbytes;
"87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hex_u;
constexpr auto ed_sk2 =
"5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hex_u;
constexpr auto ed_sk_invalid = "010203040506070809"_hex_u;

auto seed1 = session::ed25519::seed_for_ed_privkey(to_span(ed_sk1));
auto seed2 = session::ed25519::seed_for_ed_privkey(to_span(ed_sk2));
CHECK_THROWS(session::ed25519::seed_for_ed_privkey(to_span(ed_sk_invalid)));
auto seed1 = session::ed25519::seed_for_ed_privkey(ed_sk1);
auto seed2 = session::ed25519::seed_for_ed_privkey(ed_sk2);
CHECK_THROWS(session::ed25519::seed_for_ed_privkey(ed_sk_invalid));

CHECK(oxenc::to_hex(seed1.begin(), seed1.end()) ==
CHECK(oxenc::to_hex(seed1) ==
"4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7");
CHECK(oxenc::to_hex(seed2.begin(), seed2.end()) ==
CHECK(oxenc::to_hex(seed2) ==
"5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876");
}

TEST_CASE("Ed25519 pro key pair generation seed", "[ed25519][keypair]") {
using namespace session;

// Test vectors generated from Python
//
// clang-format off
//
// import nacl.bindings
// import hashlib
// import os
//
// seed0 = os.urandom(32)
// seed1 = hashlib.blake2b(seed0, key=b'SessionProRandom', digest_size=32).digest()
// (pkey, skey) = nacl.bindings.crypto_sign_seed_keypair(seed=seed0)
// (pro_pkey, pro_skey) = nacl.bindings.crypto_sign_seed_keypair(seed=seed1)
//
// print(f'Seed0: {seed0.hex()}')
// print(f'Pro: {bytes(pro_skey)[:32].hex()} / {bytes(pro_pkey).hex()}')
//
// Output
//
// Seed0: e5481635020d6f7b327e94e6d63e33a431fccabc4d2775845c43a8486a9f2884
// Pro: a4ec87e2346b25ee6394211cb682640a09dd8d297016fe241fe5b06fefef416c / b6d20c075eddd2edb69d4d7da9b7e580f187ce0537585da2b5e454b77980d0c8
//
// Seed0: 743d646706b6b04b97b752036dd6cf5f2adc4b339fcfdfb4b496f0764bb93a84
// Pro: 7da256ba427cf5419cefea81f8ebb3395c261e4dfc2c91ee4d3ce9def67aa21c / 539d0a3be9658ebb6ba3ce97b25d4f6b716f7ef6d6ae6343bd0733519f5a51e8
//
// clang-format on

constexpr auto seed1 = "e5481635020d6f7b327e94e6d63e33a431fccabc4d2775845c43a8486a9f2884"_hex_u;
constexpr auto seed2 = "743d646706b6b04b97b752036dd6cf5f2adc4b339fcfdfb4b496f0764bb93a84"_hex_u;
constexpr auto seed_invalid = "010203040506070809"_hex_u;

auto sk1 = session::ed25519::ed25519_pro_key_pair_for_ed25519_seed(seed1);
auto sk2 = session::ed25519::ed25519_pro_key_pair_for_ed25519_seed(seed2);
CHECK_THROWS(session::ed25519::ed25519_pro_key_pair_for_ed25519_seed(seed_invalid));

CHECK(sk1.size() == 64);
CHECK(sk1 != sk2);

auto kp_sk1 =
"a4ec87e2346b25ee6394211cb682640a09dd8d297016fe241fe5b06fefef416c"
"b6d20c075eddd2edb69d4d7da9b7e580f187ce0537585da2b5e454b77980d0c8";
auto kp_sk2 =
"7da256ba427cf5419cefea81f8ebb3395c261e4dfc2c91ee4d3ce9def67aa21c"
"539d0a3be9658ebb6ba3ce97b25d4f6b716f7ef6d6ae6343bd0733519f5a51e8";

CHECK(oxenc::to_hex(sk1) == kp_sk1);
CHECK(oxenc::to_hex(sk2) == kp_sk2);
}

TEST_CASE("Ed25519", "[ed25519][signature]") {
using namespace session;

auto ed_seed = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes;
auto ed_pk = "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes;
auto ed_invalid = "010203040506070809"_hexbytes;
constexpr auto ed_seed =
"4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hex_u;
constexpr auto ed_pk = "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hex_u;
constexpr auto ed_invalid = "010203040506070809"_hex_u;

auto sig1 = session::ed25519::sign(to_span(ed_seed), to_span("hello"));
CHECK_THROWS(session::ed25519::sign(to_span(ed_invalid), to_span("hello")));
auto sig1 = session::ed25519::sign(ed_seed, to_span("hello"));
CHECK_THROWS(session::ed25519::sign(ed_invalid, to_span("hello")));

auto expected_sig_hex =
"e03b6e87a53d83f202f2501e9b52193dbe4a64c6503f88244948dee53271"
"85011574589aa7b59bc9757f9b9c31b7be9c9212b92ac7c81e029ee21c338ee12405";
CHECK(oxenc::to_hex(sig1.begin(), sig1.end()) == expected_sig_hex);
CHECK(oxenc::to_hex(sig1) == expected_sig_hex);

CHECK(session::ed25519::verify(sig1, ed_pk, to_span("hello")));
CHECK_THROWS(session::ed25519::verify(ed_invalid, ed_pk, to_span("hello")));
Expand Down