1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
//! Ed25519 identity and packet signing.
//!
//! Each node has an Ed25519 keypair:
//! - **Private key** (32 bytes): never leaves the node.
//! Used to sign every outgoing packet.
//! - **Public key** (32 bytes): shared with peers.
//! Used to verify incoming packets.
//! - **NodeId** = public key (32 bytes). Direct 1:1
//! binding, no hashing.
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use crate::id::NodeId;
/// Ed25519 signature size (64 bytes).
pub const SIGNATURE_SIZE: usize = 64;
/// Ed25519 public key size (32 bytes).
pub const PUBLIC_KEY_SIZE: usize = 32;
/// A node's cryptographic identity.
///
/// Contains the Ed25519 keypair and the derived NodeId.
/// The NodeId is the public key directly (32 bytes)
/// deterministically bound to the keypair.
pub struct Identity {
signing_key: SigningKey,
verifying_key: VerifyingKey,
node_id: NodeId,
}
impl Identity {
/// Generate a new random identity.
pub fn generate() -> Self {
let mut seed = [0u8; 32];
crate::sys::random_bytes(&mut seed);
Self::from_seed(seed)
}
/// Create an identity from a 32-byte seed.
///
/// Deterministic: same seed → same keypair → same
/// NodeId.
pub fn from_seed(seed: [u8; 32]) -> Self {
let signing_key = SigningKey::from_bytes(&seed);
let verifying_key = signing_key.verifying_key();
let node_id = NodeId::from_bytes(*verifying_key.as_bytes());
Self {
signing_key,
verifying_key,
node_id,
}
}
/// The node's 256-bit ID (= public key).
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
/// The Ed25519 public key (32 bytes).
pub fn public_key(&self) -> &[u8; PUBLIC_KEY_SIZE] {
self.verifying_key.as_bytes()
}
/// Sign data with the private key.
///
/// Returns a 64-byte Ed25519 signature.
pub fn sign(&self, data: &[u8]) -> [u8; SIGNATURE_SIZE] {
let sig = self.signing_key.sign(data);
sig.to_bytes()
}
/// Verify a signature against a public key.
///
/// This is a static method — used to verify packets
/// from other nodes using their public key.
pub fn verify(
public_key: &[u8; PUBLIC_KEY_SIZE],
data: &[u8],
signature: &[u8],
) -> bool {
// Length check is not timing-sensitive (length is
// public). ed25519_dalek::verify() is constant-time.
if signature.len() != SIGNATURE_SIZE {
return false;
}
let Ok(vk) = VerifyingKey::from_bytes(public_key) else {
return false;
};
let mut sig_bytes = [0u8; SIGNATURE_SIZE];
sig_bytes.copy_from_slice(signature);
let sig = Signature::from_bytes(&sig_bytes);
vk.verify(data, &sig).is_ok()
}
}
impl std::fmt::Debug for Identity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Identity({})", self.node_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_unique() {
let a = Identity::generate();
let b = Identity::generate();
assert_ne!(a.node_id(), b.node_id());
}
#[test]
fn from_seed_deterministic() {
let seed = [0x42u8; 32];
let a = Identity::from_seed(seed);
let b = Identity::from_seed(seed);
assert_eq!(a.node_id(), b.node_id());
assert_eq!(a.public_key(), b.public_key());
}
#[test]
fn node_id_is_pubkey() {
let id = Identity::generate();
let expected = NodeId::from_bytes(*id.public_key());
assert_eq!(*id.node_id(), expected);
}
#[test]
fn sign_verify() {
let id = Identity::generate();
let data = b"hello world";
let sig = id.sign(data);
assert!(Identity::verify(id.public_key(), data, &sig));
}
#[test]
fn verify_wrong_data() {
let id = Identity::generate();
let sig = id.sign(b"correct");
assert!(!Identity::verify(id.public_key(), b"wrong", &sig));
}
#[test]
fn verify_wrong_key() {
let id1 = Identity::generate();
let id2 = Identity::generate();
let sig = id1.sign(b"data");
assert!(!Identity::verify(id2.public_key(), b"data", &sig));
}
#[test]
fn verify_truncated_sig() {
let id = Identity::generate();
assert!(!Identity::verify(
id.public_key(),
b"data",
&[0u8; 10] // too short
));
}
#[test]
fn signature_size() {
let id = Identity::generate();
let sig = id.sign(b"test");
assert_eq!(sig.len(), SIGNATURE_SIZE);
}
}
|