syncweb_core/node/
signed_message.rs1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
2use iroh::PublicKey;
3
4use crate::error::{Result, SyncwebError};
5
6const HEADER_LEN: usize = 96;
8
9#[derive(Copy, Clone)]
17#[non_exhaustive]
18pub struct SignedMessage;
19
20impl SignedMessage {
21 #[must_use]
26 pub fn sign(secret_key: &SigningKey, content: &[u8]) -> Vec<u8> {
27 let signature = secret_key.sign(content);
28 let mut wire = Vec::with_capacity(HEADER_LEN.saturating_add(content.len()));
29 wire.extend_from_slice(&secret_key.verifying_key().to_bytes());
30 wire.extend_from_slice(&signature.to_bytes());
31 wire.extend_from_slice(content);
32 wire
33 }
34
35 pub fn verify(wire_bytes: &[u8]) -> Result<(VerifyingKey, Vec<u8>)> {
44 if wire_bytes.len() < HEADER_LEN {
45 return Err(SyncwebError::operation(
46 "signed message too short",
47 format!("got {} bytes, need at least {}", wire_bytes.len(), HEADER_LEN),
48 ));
49 }
50
51 let (key_bytes, rest) = wire_bytes.split_at(32);
52 let (sig_bytes, content) = rest.split_at(64);
53
54 let verifying_key = VerifyingKey::from_bytes(
55 key_bytes
56 .try_into()
57 .map_err(|error| SyncwebError::operation("invalid verifying key bytes", error))?,
58 )
59 .map_err(|error| SyncwebError::operation("invalid Ed25519 verifying key", error))?;
60
61 let signature = Signature::from_bytes(
62 sig_bytes
63 .try_into()
64 .map_err(|error| SyncwebError::operation("invalid signature bytes", error))?,
65 );
66
67 verifying_key
68 .verify(content, &signature)
69 .map_err(|error| SyncwebError::operation("gossip message signature verification failed", error))?;
70
71 Ok((verifying_key, content.to_vec()))
72 }
73
74 pub fn verify_with_expected(wire_bytes: &[u8], expected_node_id: &PublicKey) -> Result<Vec<u8>> {
81 let (verifying_key, content) = Self::verify(wire_bytes)?;
82 let actual_node_id = PublicKey::from_bytes(&verifying_key.to_bytes())
83 .map_err(|error| SyncwebError::operation("failed to derive NodeId from verifying key", error))?;
84 if actual_node_id != *expected_node_id {
85 return Err(SyncwebError::operation(
86 "gossip message NodeId mismatch",
87 format!("expected {expected_node_id}, got {actual_node_id}"),
88 ));
89 }
90 Ok(content)
91 }
92
93 #[must_use]
95 pub const fn header_len() -> usize {
96 HEADER_LEN
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn random_signing_key() -> SigningKey {
105 use std::sync::atomic::{AtomicU64, Ordering};
106 static COUNTER: AtomicU64 = AtomicU64::new(0);
107 let mut seed = [0_u8; 32];
108 let count = COUNTER.fetch_add(1, Ordering::Relaxed);
109 seed[..8].copy_from_slice(&count.to_le_bytes());
110 SigningKey::from_bytes(&seed)
111 }
112
113 #[test]
114 fn test_sign_and_verify_roundtrip() {
115 let secret = random_signing_key();
116 let content = b"hello gossip";
117
118 let wire = SignedMessage::sign(&secret, content);
119 let (key, decoded) = SignedMessage::verify(&wire).unwrap();
120
121 assert_eq!(decoded, content);
122 assert_eq!(key, secret.verifying_key());
123 }
124
125 #[test]
126 fn test_verify_rejects_tampered_content() {
127 let secret = random_signing_key();
128 let wire = SignedMessage::sign(&secret, b"hello");
129
130 let mut tampered = wire;
131 *tampered
132 .get_mut(HEADER_LEN)
133 .expect("tampered should be long enough to have HEADER_LEN") ^= 0xFF; assert!(SignedMessage::verify(&tampered).is_err());
135 }
136
137 #[test]
138 fn test_verify_rejects_tampered_signature() {
139 let secret = random_signing_key();
140 let wire = SignedMessage::sign(&secret, b"hello");
141
142 let mut tampered = wire;
143 *tampered
144 .get_mut(40)
145 .expect("tampered should be long enough to have byte at index 40") ^= 0xFF; assert!(SignedMessage::verify(&tampered).is_err());
147 }
148
149 #[test]
150 fn test_verify_rejects_short_message() {
151 assert!(SignedMessage::verify(&[0_u8; 32]).is_err());
152 assert!(SignedMessage::verify(&[0_u8; 95]).is_err());
153 }
154
155 #[test]
156 fn test_verify_rejects_empty_message() {
157 assert!(SignedMessage::verify(&[]).is_err());
158 }
159
160 #[test]
161 fn test_node_id_derived_from_verifying_key() {
162 let secret = random_signing_key();
163 let wire = SignedMessage::sign(&secret, b"hello");
164
165 let (key, _) = SignedMessage::verify(&wire).unwrap();
166
167 let node_id = PublicKey::from_bytes(&key.to_bytes()).unwrap();
169 let expected = PublicKey::from_bytes(&secret.verifying_key().to_bytes()).unwrap();
170 assert_eq!(node_id, expected);
171 }
172
173 #[test]
174 fn test_verify_with_expected_success() {
175 let secret = random_signing_key();
176 let node_id = PublicKey::from_bytes(&secret.verifying_key().to_bytes()).unwrap();
177 let wire = SignedMessage::sign(&secret, b"hello");
178
179 let content = SignedMessage::verify_with_expected(&wire, &node_id).unwrap();
180 assert_eq!(content, b"hello");
181 }
182
183 #[test]
184 fn test_verify_with_expected_rejects_wrong_key() {
185 let alice = random_signing_key();
186 let bob = random_signing_key();
187 let bob_node_id = PublicKey::from_bytes(&bob.verifying_key().to_bytes()).unwrap();
188
189 let wire = SignedMessage::sign(&alice, b"hello");
190 assert!(SignedMessage::verify_with_expected(&wire, &bob_node_id).is_err());
191 }
192
193 #[test]
194 fn test_header_len_constant() {
195 assert_eq!(SignedMessage::header_len(), 96);
196 }
197}