Skip to content

Commit fc3ef1d

Browse files
committed
fix(p2p): make NullCrypto usable and stabilize handshake
1 parent 30fc04b commit fc3ef1d

7 files changed

Lines changed: 184 additions & 62 deletions

File tree

include/vix/p2p/Crypto.hpp

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include <span>
1818
#include <vector>
1919
#include <string>
20+
#include <array>
21+
#include <random>
2022

2123
namespace vix::p2p
2224
{
@@ -68,16 +70,56 @@ namespace vix::p2p
6870
class NullCrypto final : public Crypto
6971
{
7072
public:
71-
KeyPair generate_keypair() override { return {}; }
73+
KeyPair generate_keypair() override
74+
{
75+
KeyPair kp;
76+
kp.public_key.resize(32);
77+
kp.private_key.resize(32);
78+
79+
static thread_local std::mt19937_64 rng{std::random_device{}()};
80+
81+
auto fill = [&](std::vector<std::uint8_t> &v)
82+
{
83+
for (size_t i = 0; i < v.size(); ++i)
84+
v[i] = static_cast<std::uint8_t>(rng() & 0xFF);
85+
};
86+
87+
fill(kp.public_key);
88+
fill(kp.private_key);
7289

73-
std::vector<std::uint8_t> sign(std::span<const std::uint8_t>, std::span<const std::uint8_t>) override
90+
bool all0 = true;
91+
for (auto b : kp.public_key)
92+
{
93+
if (b != 0)
94+
{
95+
all0 = false;
96+
break;
97+
}
98+
}
99+
if (all0)
100+
kp.public_key[0] = 1;
101+
102+
return kp;
103+
}
104+
105+
std::vector<std::uint8_t> sign(std::span<const std::uint8_t> data,
106+
std::span<const std::uint8_t> private_key) override
74107
{
75-
return {};
108+
// DEV: signature = kdf_32(data || priv) (non secure)
109+
std::vector<std::uint8_t> t;
110+
t.reserve(data.size() + private_key.size());
111+
t.insert(t.end(), data.begin(), data.end());
112+
t.insert(t.end(), private_key.begin(), private_key.end());
113+
return kdf_32(t);
76114
}
77115

78-
bool verify(std::span<const std::uint8_t>, std::span<const std::uint8_t>, std::span<const std::uint8_t>) override
116+
bool verify(std::span<const std::uint8_t> data,
117+
std::span<const std::uint8_t> signature,
118+
std::span<const std::uint8_t> public_key) override
79119
{
80-
return true;
120+
(void)data;
121+
(void)public_key;
122+
return !signature.empty();
81123
}
82124

83125
std::vector<std::uint8_t> encrypt(std::span<const std::uint8_t> plaintext) override

include/vix/p2p/Transport.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace vix::p2p
4242
virtual void close() = 0;
4343
virtual TransportStats stats() const = 0;
4444
virtual std::string endpoint_string() const = 0;
45+
virtual void set_peer_id(std::string peer_id) = 0;
4546
};
4647

4748
} // namespace vix::p2p

include/vix/p2p/messages/Hello.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@
2323

2424
namespace vix::p2p::msg
2525
{
26-
// Hello = HelloInit (A -> B)
26+
// Hello (initiator -> responder)
2727
struct Hello
2828
{
2929
// anti-replay / handshake v2
3030
std::uint64_t nonce_a{0};
3131
std::uint64_t ts_ms{0};
32+
3233
// identity
3334
std::string node_id;
35+
3436
// capabilities
3537
std::unordered_map<std::string, std::string> capabilities;
36-
// crypto
38+
39+
// crypto: sender public key
3740
std::vector<std::uint8_t> public_key;
3841

3942
std::vector<std::uint8_t> encode() const
@@ -43,6 +46,7 @@ namespace vix::p2p::msg
4346
w.var_u64(nonce_a);
4447
w.var_u64(ts_ms);
4548
w.str_var(node_id);
49+
4650
w.var_u64((std::uint64_t)capabilities.size());
4751
for (const auto &[k, v] : capabilities)
4852
{

include/vix/p2p/messages/HelloAck.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,22 @@
2121

2222
namespace vix::p2p::msg
2323
{
24-
// HelloAck (B -> A)
24+
// HelloAck (responder -> initiator)
2525
struct HelloAck
2626
{
2727
std::uint64_t nonce_a{0}; // echo
2828
std::uint64_t nonce_b{0}; // challenge
2929

30+
// NEW: responder public key (needed by initiator to derive session key)
31+
std::vector<std::uint8_t> public_key;
32+
3033
std::vector<std::uint8_t> encode() const
3134
{
3235
bin::Writer w;
33-
w.reserve(24);
36+
w.reserve(64);
3437
w.var_u64(nonce_a);
3538
w.var_u64(nonce_b);
39+
w.bytes_var(public_key);
3640
return std::move(w.out);
3741
}
3842

@@ -42,6 +46,7 @@ namespace vix::p2p::msg
4246
HelloAck a;
4347
a.nonce_a = r.var_u64();
4448
a.nonce_b = r.var_u64();
49+
a.public_key = r.bytes_var();
4550

4651
if (r.remaining() != 0)
4752
throw bin::Error("HelloAck: trailing bytes");

include/vix/p2p/messages/HelloFinish.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121

2222
namespace vix::p2p::msg
2323
{
24-
// HelloFinish (A -> B)
24+
// HelloFinish (initiator -> responder)
2525
struct HelloFinish
2626
{
2727
std::uint64_t nonce_a{0};
2828
std::uint64_t nonce_b{0};
29+
2930
// signature bytes (ed25519 later); for now can be empty in NullCrypto mode
3031
std::vector<std::uint8_t> signature;
3132

src/Node.cpp

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#include <vix/p2p/messages/Hello.hpp>
3838
#include <vix/p2p/messages/Ping.hpp>
3939
#include <vix/p2p/messages/Pong.hpp>
40+
#include <vix/p2p/messages/HelloAck.hpp>
41+
#include <vix/p2p/messages/HelloFinish.hpp>
4042

4143
#include <vix/p2p/transport/Tcp.hpp>
4244
#include <vix/p2p/Bootstrap.hpp>
@@ -428,7 +430,6 @@ namespace vix::p2p
428430
}
429431

430432
schedule_handshake_timeout_(pid);
431-
send_hello(pid);
432433
}
433434

434435
bool has_peer_by_endpoint_unlocked_(const PeerEndpoint &ep) const
@@ -521,71 +522,107 @@ namespace vix::p2p
521522

522523
void on_hello(const PeerId &peer_id, const msg::Hello &h)
523524
{
524-
std::scoped_lock lk(mu_);
525+
Envelope out_env;
526+
527+
{
528+
std::scoped_lock lk(mu_);
529+
530+
auto &peer = peers_[peer_id];
531+
532+
auto now_ms = now_ms_();
533+
if (std::llabs((long long)now_ms - (long long)h.ts_ms) > 60'000)
534+
throw std::runtime_error("hello replay");
525535

526-
auto &peer = peers_[peer_id];
536+
peer.handshake.emplace();
537+
peer.handshake->stage = HandshakeState::Stage::HelloReceived;
538+
peer.handshake->nonce_a = h.nonce_a;
539+
peer.handshake->ts_ms = h.ts_ms;
540+
peer.handshake->started_at = std::chrono::steady_clock::now();
527541

528-
auto now_ms = now_ms_();
529-
if (std::llabs((long long)now_ms - (long long)h.ts_ms) > 60'000)
530-
throw std::runtime_error("hello replay");
542+
peer.meta.public_key = h.public_key;
531543

532-
peer.handshake.emplace();
533-
peer.handshake->stage = HandshakeState::Stage::HelloReceived;
534-
peer.handshake->nonce_a = h.nonce_a;
535-
peer.handshake->ts_ms = h.ts_ms;
536-
peer.handshake->started_at = std::chrono::steady_clock::now();
537-
peer.meta.public_key = h.public_key;
544+
constexpr std::size_t kMinPubKey = 16;
545+
constexpr std::size_t kMaxPubKey = 2048;
546+
if (peer.meta.public_key.size() < kMinPubKey || peer.meta.public_key.size() > kMaxPubKey)
547+
throw std::runtime_error("bad public_key size");
538548

539-
constexpr std::size_t kMinPubKey = 16;
540-
constexpr std::size_t kMaxPubKey = 2048;
549+
msg::HelloAck ack;
550+
ack.nonce_a = h.nonce_a;
551+
ack.nonce_b = rand_u64_();
541552

542-
if (peer.meta.public_key.size() < kMinPubKey || peer.meta.public_key.size() > kMaxPubKey)
543-
throw std::runtime_error("bad public_key size");
553+
ack.public_key = self_keys_.public_key;
544554

545-
msg::HelloAck ack;
546-
ack.nonce_a = h.nonce_a;
547-
ack.nonce_b = rand_u64_();
548-
peer.handshake->nonce_b = ack.nonce_b;
549-
peer.handshake->stage = HandshakeState::Stage::AckSent;
550-
send_envelope(peer_id, pack::make_envelope(MessageType::HelloAck, ack));
555+
peer.handshake->nonce_b = ack.nonce_b;
556+
peer.handshake->stage = HandshakeState::Stage::AckSent;
557+
558+
out_env = pack::make_envelope(MessageType::HelloAck, ack);
559+
}
560+
561+
// ✅ send OUTSIDE the lock (avoid deadlock)
562+
send_envelope(peer_id, out_env);
551563
}
552564

553565
void on_hello_ack(const PeerId &peer_id, const msg::HelloAck &a)
554566
{
555-
std::scoped_lock lk(mu_);
567+
Envelope out_env;
556568

557-
auto &peer = peers_[peer_id];
569+
{
570+
std::scoped_lock lk(mu_);
558571

559-
if (!peer.handshake)
560-
throw std::runtime_error("missing handshake state");
572+
auto &peer = peers_[peer_id];
561573

562-
auto &hs = *peer.handshake;
574+
if (!peer.handshake)
575+
throw std::runtime_error("missing handshake state");
563576

564-
if (hs.ts_ms == 0)
565-
throw std::runtime_error("hs missing ts_ms");
577+
auto &hs = *peer.handshake;
566578

567-
if (hs.stage != HandshakeState::Stage::HelloSent)
568-
throw std::runtime_error("unexpected HelloAck");
579+
if (hs.ts_ms == 0)
580+
throw std::runtime_error("hs missing ts_ms");
569581

570-
if (a.nonce_a != hs.nonce_a)
571-
throw std::runtime_error("nonce mismatch");
582+
if (hs.stage != HandshakeState::Stage::HelloSent)
583+
throw std::runtime_error("unexpected HelloAck");
572584

573-
hs.nonce_b = a.nonce_b;
574-
hs.stage = HandshakeState::Stage::AckReceived;
585+
if (a.nonce_a != hs.nonce_a)
586+
throw std::runtime_error("nonce mismatch");
575587

576-
auto data = make_handshake_bytes_(
577-
hs.nonce_a,
578-
hs.nonce_b,
579-
hs.ts_ms,
580-
kProto_.major,
581-
kProto_.minor,
582-
kTransport_());
588+
peer.meta.public_key = a.public_key;
589+
590+
constexpr std::size_t kMinPubKey = 16;
591+
constexpr std::size_t kMaxPubKey = 2048;
592+
if (peer.meta.public_key.size() < kMinPubKey || peer.meta.public_key.size() > kMaxPubKey)
593+
throw std::runtime_error("bad public_key size");
594+
595+
hs.nonce_b = a.nonce_b;
596+
hs.stage = HandshakeState::Stage::AckReceived;
597+
598+
auto data = make_handshake_bytes_(
599+
hs.nonce_a,
600+
hs.nonce_b,
601+
hs.ts_ms,
602+
kProto_.major,
603+
kProto_.minor,
604+
kTransport_());
605+
606+
msg::HelloFinish fin;
607+
fin.nonce_a = hs.nonce_a;
608+
fin.nonce_b = hs.nonce_b;
609+
fin.signature = crypto_->sign(data, self_keys_.private_key);
610+
611+
auto sk = derive_session_key_unlocked_(hs, peer.meta.public_key);
612+
613+
peer.meta.session_key_32 = std::move(sk);
614+
peer.meta.secure = true;
615+
peer.meta.send_nonce_counter = 1;
616+
617+
peer.state = PeerState::Connected;
618+
peer.handshake.reset();
619+
stats_.handshakes_completed++;
620+
621+
out_env = pack::make_envelope(MessageType::HelloFinish, fin);
622+
}
583623

584-
msg::HelloFinish fin;
585-
fin.nonce_a = hs.nonce_a;
586-
fin.nonce_b = hs.nonce_b;
587-
fin.signature = crypto_->sign(data, self_keys_.private_key);
588-
send_envelope(peer_id, pack::make_envelope(MessageType::HelloFinish, fin));
624+
// ✅ send OUTSIDE lock
625+
send_envelope(peer_id, out_env);
589626
}
590627

591628
void on_hello_finish(const PeerId &peer_id, const msg::HelloFinish &f)
@@ -725,10 +762,18 @@ namespace vix::p2p
725762
return;
726763
}
727764
}
765+
catch (const std::exception &e)
766+
{
767+
if (!running_)
768+
return;
769+
log_(std::string("[p2p] envelope error: ") + e.what() + " peer_id=" + peer_id);
770+
disconnect_impl_(peer_id);
771+
}
728772
catch (...)
729773
{
730774
if (!running_)
731775
return;
776+
log_(std::string("[p2p] envelope error: unknown exception peer_id=") + peer_id);
732777
disconnect_impl_(peer_id);
733778
}
734779
}
@@ -756,11 +801,15 @@ namespace vix::p2p
756801
h.node_id = cfg_.node_id;
757802
h.public_key = self_keys_.public_key;
758803

759-
peers_[peer_id].handshake.emplace();
760-
peers_[peer_id].handshake->nonce_a = h.nonce_a;
761-
peers_[peer_id].handshake->ts_ms = h.ts_ms;
762-
peers_[peer_id].handshake->stage = HandshakeState::Stage::HelloSent;
763-
peers_[peer_id].handshake->started_at = std::chrono::steady_clock::now();
804+
{
805+
std::scoped_lock lk(mu_);
806+
auto &p = peers_[peer_id];
807+
p.handshake.emplace();
808+
p.handshake->nonce_a = h.nonce_a;
809+
p.handshake->ts_ms = h.ts_ms;
810+
p.handshake->stage = HandshakeState::Stage::HelloSent;
811+
p.handshake->started_at = std::chrono::steady_clock::now();
812+
}
764813

765814
send_envelope(peer_id, pack::make_envelope(MessageType::Hello, h));
766815
}
@@ -946,6 +995,10 @@ namespace vix::p2p
946995
transports_.erase(tit);
947996

948997
p.id = new_id;
998+
999+
if (t)
1000+
t->set_peer_id(new_id);
1001+
9491002
peers_.emplace(new_id, std::move(p));
9501003
transports_.emplace(new_id, std::move(t));
9511004
}

0 commit comments

Comments
 (0)