dkls23/proto/signed.rs
1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! Module for handling signed messages in the protocol.
5//!
6//! This module provides functionality for creating and verifying signed messages
7//! with support for additional data and trailers. It uses a pluggable signature
8//! scheme interface to allow for different signature implementations.
9
10use std::marker::PhantomData;
11use std::ops::Range;
12
13use bytemuck::{AnyBitPattern, NoUninit};
14use signature::{SignatureEncoding, Signer, Verifier};
15
16use sl_mpc_mate::message::*;
17
18/// A wrapper for a message of type T with support for in-place signing and verifying.
19///
20/// This struct provides functionality for creating and verifying signed messages
21/// with the following format:
22///
23/// ```text
24/// [ msg-hdr | payload | trailer | signature ]
25/// ```
26///
27/// Where:
28/// - `msg-hdr`: Message header containing ID, TTL, and flags
29/// - `payload`: The message payload of type T
30/// - `trailer`: Optional additional data
31/// - `signature`: The cryptographic signature
32///
33/// # Type Parameters
34/// * `T` - The type of the message payload
35/// * `S` - The type of signature encoding used
36pub struct SignedMessage<T, S: SignatureEncoding> {
37 buffer: Vec<u8>,
38 marker: PhantomData<(T, <S as SignatureEncoding>::Repr)>,
39}
40
41impl<S: SignatureEncoding, T: AnyBitPattern + NoUninit> SignedMessage<T, S> {
42 /// Size of the message header in bytes.
43 pub const HEADER_SIZE: usize = MESSAGE_HEADER_SIZE;
44
45 const T_SIZE: usize = core::mem::size_of::<T>();
46 const S_SIZE: usize = core::mem::size_of::<S::Repr>();
47
48 /// Calculates the total size of a signed message.
49 ///
50 /// # Arguments
51 /// * `trailer` - Size of the trailer data in bytes
52 ///
53 /// # Returns
54 /// The total size in bytes needed for the signed message
55 pub const fn size(trailer: usize) -> usize {
56 MESSAGE_HEADER_SIZE + Self::T_SIZE + trailer + Self::S_SIZE
57 }
58
59 /// Creates a new signed message with the specified parameters.
60 ///
61 /// # Arguments
62 /// * `id` - Message identifier
63 /// * `ttl` - Time-to-live value
64 /// * `flags` - Message flags
65 /// * `trailer` - Size of trailer data in bytes
66 ///
67 /// # Returns
68 /// A new `SignedMessage` instance
69 pub fn new(id: &MsgId, ttl: u32, flags: u16, trailer: usize) -> Self {
70 let buffer = vec![0u8; Self::size(trailer)];
71
72 Self::from_buffer(buffer, id, ttl, flags, trailer)
73 }
74
75 /// Creates a signed message from an existing buffer.
76 ///
77 /// # Arguments
78 /// * `buffer` - Existing buffer to use
79 /// * `id` - Message identifier
80 /// * `ttl` - Time-to-live value
81 /// * `flags` - Message flags
82 /// * `trailer` - Size of trailer data in bytes
83 ///
84 /// # Returns
85 /// A new `SignedMessage` instance using the provided buffer
86 pub fn from_buffer(
87 mut buffer: Vec<u8>,
88 id: &MsgId,
89 ttl: u32,
90 flags: u16,
91 trailer: usize,
92 ) -> Self {
93 buffer.resize(Self::size(trailer), 0);
94
95 if let Some(hdr) = buffer.first_chunk_mut::<MESSAGE_HEADER_SIZE>() {
96 MsgHdr::encode(hdr, id, ttl, flags);
97 }
98
99 Self {
100 buffer,
101 marker: PhantomData,
102 }
103 }
104
105 /// Returns mutable references to the message payload and trailer.
106 ///
107 /// # Returns
108 /// A tuple containing:
109 /// - Mutable reference to the payload object
110 /// - Mutable reference to the trailer bytes
111 pub fn payload(&mut self) -> (&mut T, &mut [u8]) {
112 let end = self.buffer.len() - Self::S_SIZE;
113
114 let body = &mut self.buffer[MESSAGE_HEADER_SIZE..end];
115 let (msg, trailer) = body.split_at_mut(Self::T_SIZE);
116
117 (bytemuck::from_bytes_mut(msg), trailer)
118 }
119
120 /// Signs the message and returns the underlying byte vector.
121 ///
122 /// # Arguments
123 /// * `signing_key` - The key used to sign the message
124 ///
125 /// # Returns
126 /// The signed message as a byte vector
127 pub fn sign<K: Signer<S>>(self, signing_key: &K) -> Vec<u8> {
128 let mut buffer = self.buffer;
129
130 let last = buffer.len() - Self::S_SIZE;
131 let (msg, tail) = buffer.split_at_mut(last);
132
133 let sign = signing_key.sign(msg).to_bytes();
134
135 tail.copy_from_slice(sign.as_ref());
136
137 buffer
138 }
139
140 /// Builds and signs a message using a closure to set the payload.
141 ///
142 /// # Arguments
143 /// * `id` - Message identifier
144 /// * `ttl` - Time-to-live value
145 /// * `trailer` - Size of trailer data in bytes
146 /// * `signing_key` - The key used to sign the message
147 /// * `f` - Closure that sets the payload and trailer content
148 ///
149 /// # Returns
150 /// The signed message as a byte vector
151 pub fn build<F, K: Signer<S>>(
152 id: &MsgId,
153 ttl: u32,
154 trailer: usize,
155 signing_key: &K,
156 f: F,
157 ) -> Vec<u8>
158 where
159 F: FnOnce(&mut T, &mut [u8]),
160 {
161 let mut msg = Self::new(id, ttl, 0, trailer);
162 let (payload, trailer) = msg.payload();
163 f(payload, trailer);
164 msg.sign(signing_key)
165 }
166
167 /// Verifies a signed message and returns references to the payload and trailer.
168 ///
169 /// # Arguments
170 /// * `buffer` - The signed message buffer
171 /// * `trailer` - Size of trailer data in bytes
172 /// * `verify_key` - The key used to verify the signature
173 ///
174 /// # Returns
175 /// A tuple containing references to the payload and trailer,
176 /// or `None` if verification fails
177 pub fn verify_with_trailer<'msg, V: Verifier<S>>(
178 buffer: &'msg [u8],
179 trailer: usize,
180 verify_key: &V,
181 ) -> Option<(&'msg T, &'msg [u8])> {
182 // Make sure that buffer is exactly right size
183 if buffer.len() != Self::size(trailer) {
184 return None;
185 }
186
187 let sign_offset = buffer.len() - Self::S_SIZE;
188 let (msg, sign) = buffer.split_at(sign_offset);
189 let sign = S::try_from(sign).ok()?;
190
191 verify_key.verify(msg, &sign).ok()?;
192
193 let body = &msg[MESSAGE_HEADER_SIZE..];
194 let (payload, trailer) = body.split_at(Self::T_SIZE);
195 Some((bytemuck::from_bytes(payload), trailer))
196 }
197
198 /// Verifies a signed message and returns a reference to the payload.
199 ///
200 /// # Arguments
201 /// * `buffer` - The signed message buffer
202 /// * `verify_key` - The key used to verify the signature
203 ///
204 /// # Returns
205 /// A reference to the payload, or `None` if verification fails
206 pub fn verify<'msg, V: Verifier<S>>(
207 buffer: &'msg [u8],
208 verify_key: &V,
209 ) -> Option<&'msg T> {
210 Self::verify_with_trailer(buffer, 0, verify_key).map(|(m, _)| m)
211 }
212}
213
214impl<S: SignatureEncoding> SignedMessage<(), S> {
215 /// Verifies a message in the buffer and returns the range containing the payload.
216 ///
217 /// # Arguments
218 /// * `buffer` - The signed message buffer
219 /// * `verify_key` - The key used to verify the signature
220 ///
221 /// # Returns
222 /// The range of bytes containing the payload, or `None` if verification fails
223 pub fn verify_buffer<V: Verifier<S>>(
224 buffer: &[u8],
225 verify_key: &V,
226 ) -> Option<Range<usize>> {
227 let overhead = MESSAGE_HEADER_SIZE + Self::S_SIZE;
228
229 if buffer.len() > overhead {
230 let trailer = buffer.len() - overhead;
231
232 Self::verify_with_trailer(buffer, trailer, verify_key)?;
233
234 Some(MESSAGE_HEADER_SIZE..buffer.len() - Self::S_SIZE)
235 } else {
236 None
237 }
238 }
239}