miden_tx/auth/
tx_authenticator.rs1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::string::ToString;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6
7use miden_objects::account::AuthSecretKey;
8use miden_objects::crypto::SequentialCommit;
9use miden_objects::transaction::TransactionSummary;
10use miden_objects::{Felt, Hasher, Word};
11use miden_processor::FutureMaybeSend;
12use rand::Rng;
13use tokio::sync::RwLock;
14
15use super::signatures::get_falcon_signature;
16use crate::errors::AuthenticationError;
17use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
18
19#[derive(Debug, Clone)]
32pub enum SigningInputs {
33 TransactionSummary(Box<TransactionSummary>),
34 Arbitrary(Vec<Felt>),
35 Blind(Word),
36}
37
38impl SequentialCommit for SigningInputs {
39 type Commitment = Word;
40
41 fn to_elements(&self) -> Vec<Felt> {
42 match self {
43 SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_elements(),
44 SigningInputs::Arbitrary(elements) => elements.clone(),
45 SigningInputs::Blind(word) => word.as_elements().to_vec(),
46 }
47 }
48
49 fn to_commitment(&self) -> Self::Commitment {
50 match self {
51 SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_commitment(),
53 SigningInputs::Arbitrary(elements) => Hasher::hash_elements(elements),
55 SigningInputs::Blind(word) => *word,
57 }
58 }
59}
60
61impl SigningInputs {
63 pub fn to_commitment(&self) -> Word {
65 <Self as SequentialCommit>::to_commitment(self)
66 }
67
68 pub fn to_elements(&self) -> Vec<Felt> {
70 <Self as SequentialCommit>::to_elements(self)
71 }
72}
73
74impl Serializable for SigningInputs {
78 fn write_into<W: ByteWriter>(&self, target: &mut W) {
79 match self {
80 SigningInputs::TransactionSummary(tx_summary) => {
81 target.write_u8(0);
82 tx_summary.as_ref().write_into(target);
83 },
84 SigningInputs::Arbitrary(elements) => {
85 target.write_u8(1);
86 elements.write_into(target);
87 },
88 SigningInputs::Blind(word) => {
89 target.write_u8(2);
90 word.write_into(target);
91 },
92 }
93 }
94}
95
96impl Deserializable for SigningInputs {
97 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
98 let discriminant = source.read_u8()?;
99 match discriminant {
100 0 => {
101 let tx_summary: TransactionSummary = source.read()?;
102 Ok(SigningInputs::TransactionSummary(Box::new(tx_summary)))
103 },
104 1 => {
105 let elements: Vec<Felt> = source.read()?;
106 Ok(SigningInputs::Arbitrary(elements))
107 },
108 2 => {
109 let word: Word = source.read()?;
110 Ok(SigningInputs::Blind(word))
111 },
112 other => Err(DeserializationError::InvalidValue(format!(
113 "invalid SigningInputs variant: {other}"
114 ))),
115 }
116 }
117}
118
119pub trait TransactionAuthenticator {
130 fn get_signature(
142 &self,
143 pub_key: Word,
144 signing_inputs: &SigningInputs,
145 ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>>;
146}
147
148#[derive(Debug, Clone, Copy)]
153pub struct UnreachableAuth {
154 _protect: core::marker::PhantomData<u8>,
156}
157
158impl TransactionAuthenticator for UnreachableAuth {
159 #[allow(clippy::manual_async_fn)]
160 fn get_signature(
161 &self,
162 _pub_key: Word,
163 _signing_inputs: &SigningInputs,
164 ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>> {
165 async { unreachable!("Type `UnreachableAuth` must not be instantiated") }
166 }
167}
168
169#[derive(Clone, Debug)]
174pub struct BasicAuthenticator<R> {
175 keys: BTreeMap<Word, AuthSecretKey>,
177 rng: Arc<RwLock<R>>,
178}
179
180impl<R: Rng> BasicAuthenticator<R> {
181 #[cfg(feature = "std")]
182 pub fn new(keys: &[(Word, AuthSecretKey)]) -> BasicAuthenticator<rand::rngs::StdRng> {
183 use rand::SeedableRng;
184 use rand::rngs::StdRng;
185
186 let rng = StdRng::from_os_rng();
187 BasicAuthenticator::<StdRng>::new_with_rng(keys, rng)
188 }
189
190 pub fn new_with_rng(keys: &[(Word, AuthSecretKey)], rng: R) -> Self {
191 let mut key_map = BTreeMap::new();
192 for (word, secret_key) in keys {
193 key_map.insert(*word, secret_key.clone());
194 }
195
196 BasicAuthenticator {
197 keys: key_map,
198 rng: Arc::new(RwLock::new(rng)),
199 }
200 }
201
202 pub fn keys(&self) -> &BTreeMap<Word, AuthSecretKey> {
205 &self.keys
206 }
207}
208
209impl<R: Rng + Send + Sync> TransactionAuthenticator for BasicAuthenticator<R> {
210 fn get_signature(
220 &self,
221 pub_key: Word,
222 signing_inputs: &SigningInputs,
223 ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>> {
224 let message = signing_inputs.to_commitment();
225
226 async move {
227 let mut rng = self.rng.write().await;
228 match self.keys.get(&pub_key) {
229 Some(key) => match key {
230 AuthSecretKey::RpoFalcon512(falcon_key) => {
231 get_falcon_signature(falcon_key, message, &mut *rng)
232 },
233 },
234 None => Err(AuthenticationError::UnknownPublicKey(format!(
235 "public key {pub_key} is not contained in the authenticator's keys",
236 ))),
237 }
238 }
239 }
240}
241
242impl TransactionAuthenticator for () {
246 #[allow(clippy::manual_async_fn)]
247 fn get_signature(
248 &self,
249 _pub_key: Word,
250 _signing_inputs: &SigningInputs,
251 ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>> {
252 async {
253 Err(AuthenticationError::RejectedSignature(
254 "default authenticator cannot provide signatures".to_string(),
255 ))
256 }
257 }
258}
259
260#[cfg(test)]
261mod test {
262 use miden_lib::utils::{Deserializable, Serializable};
263 use miden_objects::account::AuthSecretKey;
264 use miden_objects::crypto::dsa::rpo_falcon512::SecretKey;
265 use miden_objects::{Felt, Word};
266
267 use super::SigningInputs;
268
269 #[test]
270 fn serialize_auth_key() {
271 let secret_key = SecretKey::new();
272 let auth_key = AuthSecretKey::RpoFalcon512(secret_key.clone());
273 let serialized = auth_key.to_bytes();
274 let deserialized = AuthSecretKey::read_from_bytes(&serialized).unwrap();
275
276 match deserialized {
277 AuthSecretKey::RpoFalcon512(key) => assert_eq!(secret_key.to_bytes(), key.to_bytes()),
278 }
279 }
280
281 #[test]
282 fn serialize_deserialize_signing_inputs_arbitrary() {
283 let elements = vec![
284 Felt::new(0),
285 Felt::new(1),
286 Felt::new(2),
287 Felt::new(3),
288 Felt::new(4),
289 Felt::new(5),
290 Felt::new(6),
291 Felt::new(7),
292 ];
293 let inputs = SigningInputs::Arbitrary(elements.clone());
294 let bytes = inputs.to_bytes();
295 let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
296
297 match decoded {
298 SigningInputs::Arbitrary(decoded_elements) => {
299 assert_eq!(decoded_elements, elements);
300 },
301 _ => panic!("expected Arbitrary variant"),
302 }
303 }
304
305 #[test]
306 fn serialize_deserialize_signing_inputs_blind() {
307 let word = Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
308 let inputs = SigningInputs::Blind(word);
309 let bytes = inputs.to_bytes();
310 let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
311
312 match decoded {
313 SigningInputs::Blind(w) => {
314 assert_eq!(w, word);
315 },
316 _ => panic!("expected Blind variant"),
317 }
318 }
319}