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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
//! TCP network protocol for coordinator ↔ signer communication.
//!
//! Wire format: 4-byte big-endian length prefix + JSON payload.
//!
//! Messages flow in both directions:
//! - Signer → Coordinator: Register, Commitment, Share
//! - Coordinator → Signer: Registered, SessionPending, CommitmentsReady, Signature
//! - Client → Coordinator: CreateSession, GetStatus
//! - Coordinator → Client: SessionCreated, Signature, Error
use crate::coordinator::session::SignerId;
use serde::{Deserialize, Serialize};
use std::io;
/// Protocol message exchanged over TCP between coordinator, signers, and clients.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ProtocolMessage {
/// Signer registers with coordinator.
Register {
/// Signer identity.
signer_id: SignerId,
/// Quorum this signer belongs to.
quorum_id: String,
},
/// Coordinator acknowledges registration.
Registered {
/// Signer identity.
signer_id: SignerId,
},
/// Client requests session creation.
CreateSession {
/// Quorum ID.
quorum_id: String,
/// Signing scheme.
scheme: String,
/// Message to sign.
message: Vec<u8>,
/// Threshold T.
threshold: u32,
/// Total parties N.
num_parties: u32,
},
/// Coordinator confirms session created.
SessionCreated {
/// Session ID.
session_id: String,
},
/// Coordinator notifies signers of pending session.
SessionPending {
/// Session ID.
session_id: String,
/// Message to sign.
message: Vec<u8>,
/// Threshold.
threshold: u32,
},
/// Signer submits commitment.
Commitment {
/// Session ID.
session_id: String,
/// Signer identity.
signer_id: SignerId,
/// Commitment bytes.
bytes: Vec<u8>,
/// Identity signature.
signature: Vec<u8>,
},
/// Coordinator notifies that commitments are collected.
CommitmentsReady {
/// Session ID.
session_id: String,
},
/// Signer submits share.
Share {
/// Session ID.
session_id: String,
/// Signer identity.
signer_id: SignerId,
/// Share bytes.
bytes: Vec<u8>,
/// Identity signature.
signature: Vec<u8>,
},
/// Coordinator returns aggregated signature.
Signature {
/// Session ID.
session_id: String,
/// Signature bytes.
bytes: Vec<u8>,
/// Algorithm.
algorithm: String,
/// Contributing signers.
contributing_signers: Vec<SignerId>,
},
/// Acknowledgement (commitment or share accepted, no further action needed).
Ack {
/// Session ID being acknowledged.
session_id: String,
},
/// Error response.
Error {
/// Error message.
message: String,
},
/// Status query.
GetStatus {
/// Session ID (optional).
session_id: Option<String>,
},
/// Status response.
Status {
/// Session ID.
session_id: String,
/// Current state.
state: String,
},
/// Liveness/readiness probe.
HealthCheck,
/// Health status response.
HealthStatus {
/// Server is running.
alive: bool,
/// Coordinator can accept sessions.
ready: bool,
/// Active session count.
session_count: usize,
/// Server uptime in seconds.
uptime_seconds: u64,
},
/// Prometheus metrics query.
MetricsQuery,
/// Prometheus metrics response (text exposition format).
MetricsResponse {
/// Prometheus text format metrics.
text: String,
},
}
/// Send a protocol message over any writable stream.
pub fn send_message<S: std::io::Write + ?Sized>(
stream: &mut S,
msg: &ProtocolMessage,
) -> io::Result<()> {
let json = serde_json::to_vec(msg)?;
let len = json.len() as u32;
stream.write_all(&len.to_be_bytes())?;
stream.write_all(&json)?;
stream.flush()?;
Ok(())
}
/// Receive a protocol message from any readable stream.
pub fn recv_message<S: std::io::Read + ?Sized>(stream: &mut S) -> io::Result<ProtocolMessage> {
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf)?;
let len = u32::from_be_bytes(len_buf) as usize;
if len > 16 * 1024 * 1024 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("message too large: {} bytes", len),
));
}
let mut json_buf = vec![0u8; len];
stream.read_exact(&mut json_buf)?;
let msg: ProtocolMessage = serde_json::from_slice(&json_buf)?;
Ok(msg)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn protocol_message_serializes() {
let msg = ProtocolMessage::Register {
signer_id: "alice".into(),
quorum_id: "test".into(),
};
let json = serde_json::to_vec(&msg).unwrap();
assert!(json.len() > 10);
let recovered: ProtocolMessage = serde_json::from_slice(&json).unwrap();
match recovered {
ProtocolMessage::Register {
signer_id,
quorum_id,
} => {
assert_eq!(signer_id, "alice");
assert_eq!(quorum_id, "test");
}
_ => panic!("wrong variant"),
}
}
#[test]
fn all_variants_round_trip() {
let messages = vec![
ProtocolMessage::Register {
signer_id: "a".into(),
quorum_id: "q".into(),
},
ProtocolMessage::Registered {
signer_id: "a".into(),
},
ProtocolMessage::CreateSession {
quorum_id: "q".into(),
scheme: "FROST-P256".into(),
message: vec![1, 2, 3],
threshold: 3,
num_parties: 5,
},
ProtocolMessage::SessionCreated {
session_id: "s1".into(),
},
ProtocolMessage::SessionPending {
session_id: "s1".into(),
message: vec![1, 2, 3],
threshold: 3,
},
ProtocolMessage::Commitment {
session_id: "s1".into(),
signer_id: "a".into(),
bytes: vec![4, 5],
signature: vec![6, 7],
},
ProtocolMessage::Signature {
session_id: "s1".into(),
bytes: vec![8, 9],
algorithm: "FROST-P256".into(),
contributing_signers: vec!["a".into(), "b".into()],
},
ProtocolMessage::Error {
message: "test".into(),
},
];
for msg in &messages {
let json = serde_json::to_vec(msg).unwrap();
let recovered: ProtocolMessage = serde_json::from_slice(&json).unwrap();
let json2 = serde_json::to_vec(&recovered).unwrap();
assert_eq!(json, json2, "round-trip must preserve bytes");
}
}
}