Skip to main content

confium_tc/coordinator/
client.rs

1//! TCP signer client — connects to coordinator, participates in signing sessions.
2//!
3//! Each signer:
4//! 1. Connects to coordinator via TCP
5//! 2. Registers with signer_id + quorum_id
6//! 3. Creates a session (or receives notification of pending session)
7//! 4. Submits commitment (round 1)
8//! 5. Submits share (round 2)
9//! 6. Receives aggregated signature (or error)
10//!
11//! Usage in e2e tests:
12//! ```no_run
13//! use confium_tc::coordinator::client::SignerClient;
14//!
15//! let mut client = SignerClient::connect("127.0.0.1:18432").unwrap();
16//! client.register("director-1", "biml-root").unwrap();
17//! client.submit_commitment("session-1", "director-1", &[0u8; 32]).unwrap();
18//! client.submit_share("session-1", "director-1", &[0u8; 32]).unwrap();
19//! ```
20
21use std::io;
22use std::net::TcpStream;
23
24use crate::coordinator::net::{ProtocolMessage, recv_message, send_message};
25
26/// TCP signer client.
27pub struct SignerClient {
28    stream: TcpStream,
29}
30
31impl SignerClient {
32    /// Connect to coordinator at `addr` (e.g., "127.0.0.1:18432").
33    pub fn connect(addr: &str) -> io::Result<Self> {
34        let stream = TcpStream::connect(addr)?;
35        Ok(Self { stream })
36    }
37
38    /// Register this signer with the coordinator.
39    pub fn register(&mut self, signer_id: &str, quorum_id: &str) -> io::Result<()> {
40        send_message(
41            &mut self.stream,
42            &ProtocolMessage::Register {
43                signer_id: signer_id.into(),
44                quorum_id: quorum_id.into(),
45            },
46        )?;
47        let resp = recv_message(&mut self.stream)?;
48        match resp {
49            ProtocolMessage::Registered { signer_id: sid } if sid == signer_id => Ok(()),
50            _ => Err(io::Error::new(
51                io::ErrorKind::InvalidData,
52                "unexpected register response",
53            )),
54        }
55    }
56
57    /// Create a new signing session on the coordinator.
58    pub fn create_session(
59        &mut self,
60        quorum_id: &str,
61        scheme: &str,
62        message: &[u8],
63        threshold: u32,
64        num_parties: u32,
65    ) -> io::Result<String> {
66        send_message(
67            &mut self.stream,
68            &ProtocolMessage::CreateSession {
69                quorum_id: quorum_id.into(),
70                scheme: scheme.into(),
71                message: message.to_vec(),
72                threshold,
73                num_parties,
74            },
75        )?;
76        let resp = recv_message(&mut self.stream)?;
77        match resp {
78            ProtocolMessage::SessionCreated { session_id } => Ok(session_id),
79            ProtocolMessage::Error { message } => {
80                Err(io::Error::other(format!("coordinator error: {message}")))
81            }
82            _ => Err(io::Error::new(
83                io::ErrorKind::InvalidData,
84                "unexpected response",
85            )),
86        }
87    }
88
89    /// Submit a commitment for a session.
90    pub fn submit_commitment(
91        &mut self,
92        session_id: &str,
93        signer_id: &str,
94        commitment_bytes: &[u8],
95    ) -> io::Result<()> {
96        send_message(
97            &mut self.stream,
98            &ProtocolMessage::Commitment {
99                session_id: session_id.into(),
100                signer_id: signer_id.into(),
101                bytes: commitment_bytes.to_vec(),
102                signature: vec![0u8; 64],
103            },
104        )?;
105        // Wait for Ack or Error
106        self.stream
107            .set_read_timeout(Some(std::time::Duration::from_secs(5)))?;
108        match recv_message(&mut self.stream) {
109            Ok(ProtocolMessage::Ack { .. }) => Ok(()),
110            Ok(ProtocolMessage::Error { message }) => {
111                Err(io::Error::other(format!("coordinator error: {message}")))
112            }
113            Ok(_) => Ok(()), // tolerate unexpected but non-error responses
114            Err(e) => Err(e),
115        }
116    }
117
118    /// Submit a share for a session. Returns the aggregated signature if
119    /// this was the T-th share (threshold met).
120    pub fn submit_share(
121        &mut self,
122        session_id: &str,
123        signer_id: &str,
124        share_bytes: &[u8],
125    ) -> io::Result<Option<Vec<u8>>> {
126        send_message(
127            &mut self.stream,
128            &ProtocolMessage::Share {
129                session_id: session_id.into(),
130                signer_id: signer_id.into(),
131                bytes: share_bytes.to_vec(),
132                signature: vec![0u8; 64],
133            },
134        )?;
135
136        // Set a short read timeout — if coordinator doesn't respond (threshold
137        // not met), the client gets WouldBlock instead of blocking forever.
138        self.stream
139            .set_read_timeout(Some(std::time::Duration::from_secs(5)))?;
140
141        match recv_message(&mut self.stream) {
142            Ok(ProtocolMessage::Signature { bytes, .. }) => Ok(Some(bytes)),
143            Ok(ProtocolMessage::Ack { .. }) => Ok(None),
144            Ok(ProtocolMessage::Error { message }) => {
145                Err(io::Error::other(format!("coordinator error: {message}")))
146            }
147            Ok(_) => Ok(None),
148            Err(ref e)
149                if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
150            {
151                Ok(None)
152            }
153            Err(e) => Err(e),
154        }
155    }
156
157    /// Query session status.
158    pub fn get_status(&mut self, session_id: &str) -> io::Result<String> {
159        send_message(
160            &mut self.stream,
161            &ProtocolMessage::GetStatus {
162                session_id: Some(session_id.into()),
163            },
164        )?;
165        let resp = recv_message(&mut self.stream)?;
166        match resp {
167            ProtocolMessage::Status { state, .. } => Ok(state),
168            _ => Err(io::Error::new(
169                io::ErrorKind::InvalidData,
170                "unexpected status response",
171            )),
172        }
173    }
174}