miden_tx/auth/
tx_authenticator.rs1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::string::ToString;
4use alloc::vec::Vec;
5
6use miden_objects::account::auth::{AuthSecretKey, PublicKeyCommitment, Signature};
7use miden_objects::crypto::SequentialCommit;
8use miden_objects::transaction::TransactionSummary;
9use miden_objects::{Felt, Hasher, Word};
10use miden_processor::FutureMaybeSend;
11
12use crate::errors::AuthenticationError;
13use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
14
15#[derive(Debug, Clone)]
28pub enum SigningInputs {
29 TransactionSummary(Box<TransactionSummary>),
30 Arbitrary(Vec<Felt>),
31 Blind(Word),
32}
33
34impl SequentialCommit for SigningInputs {
35 type Commitment = Word;
36
37 fn to_elements(&self) -> Vec<Felt> {
38 match self {
39 SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_elements(),
40 SigningInputs::Arbitrary(elements) => elements.clone(),
41 SigningInputs::Blind(word) => word.as_elements().to_vec(),
42 }
43 }
44
45 fn to_commitment(&self) -> Self::Commitment {
46 match self {
47 SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_commitment(),
49 SigningInputs::Arbitrary(elements) => Hasher::hash_elements(elements),
51 SigningInputs::Blind(word) => *word,
53 }
54 }
55}
56
57impl SigningInputs {
59 pub fn to_commitment(&self) -> Word {
61 <Self as SequentialCommit>::to_commitment(self)
62 }
63
64 pub fn to_elements(&self) -> Vec<Felt> {
66 <Self as SequentialCommit>::to_elements(self)
67 }
68}
69
70impl Serializable for SigningInputs {
74 fn write_into<W: ByteWriter>(&self, target: &mut W) {
75 match self {
76 SigningInputs::TransactionSummary(tx_summary) => {
77 target.write_u8(0);
78 tx_summary.as_ref().write_into(target);
79 },
80 SigningInputs::Arbitrary(elements) => {
81 target.write_u8(1);
82 elements.write_into(target);
83 },
84 SigningInputs::Blind(word) => {
85 target.write_u8(2);
86 word.write_into(target);
87 },
88 }
89 }
90}
91
92impl Deserializable for SigningInputs {
93 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
94 let discriminant = source.read_u8()?;
95 match discriminant {
96 0 => {
97 let tx_summary: TransactionSummary = source.read()?;
98 Ok(SigningInputs::TransactionSummary(Box::new(tx_summary)))
99 },
100 1 => {
101 let elements: Vec<Felt> = source.read()?;
102 Ok(SigningInputs::Arbitrary(elements))
103 },
104 2 => {
105 let word: Word = source.read()?;
106 Ok(SigningInputs::Blind(word))
107 },
108 other => Err(DeserializationError::InvalidValue(format!(
109 "invalid SigningInputs variant: {other}"
110 ))),
111 }
112 }
113}
114
115pub trait TransactionAuthenticator {
126 fn get_signature(
138 &self,
139 pub_key_commitment: PublicKeyCommitment,
140 signing_inputs: &SigningInputs,
141 ) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>>;
142}
143
144#[derive(Debug, Clone, Copy)]
149pub struct UnreachableAuth {
150 _protect: core::marker::PhantomData<u8>,
152}
153
154impl TransactionAuthenticator for UnreachableAuth {
155 #[allow(clippy::manual_async_fn)]
156 fn get_signature(
157 &self,
158 _pub_key_commitment: PublicKeyCommitment,
159 _signing_inputs: &SigningInputs,
160 ) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
161 async { unreachable!("Type `UnreachableAuth` must not be instantiated") }
162 }
163}
164
165#[derive(Clone, Debug)]
170pub struct BasicAuthenticator {
171 keys: BTreeMap<PublicKeyCommitment, AuthSecretKey>,
173}
174
175impl BasicAuthenticator {
176 pub fn new(keys: &[AuthSecretKey]) -> Self {
177 let mut key_map = BTreeMap::new();
178 for secret_key in keys {
179 let pub_key = secret_key.public_key().to_commitment();
180 key_map.insert(pub_key, secret_key.clone());
181 }
182
183 BasicAuthenticator { keys: key_map }
184 }
185
186 pub fn keys(&self) -> &BTreeMap<PublicKeyCommitment, AuthSecretKey> {
191 &self.keys
192 }
193}
194
195impl TransactionAuthenticator for BasicAuthenticator {
196 fn get_signature(
208 &self,
209 pub_key_commitment: PublicKeyCommitment,
210 signing_inputs: &SigningInputs,
211 ) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
212 let message = signing_inputs.to_commitment();
213
214 async move {
215 match self.keys.get(&pub_key_commitment) {
216 Some(key) => Ok(key.sign(message)),
217 None => Err(AuthenticationError::UnknownPublicKey(pub_key_commitment)),
218 }
219 }
220 }
221}
222
223impl TransactionAuthenticator for () {
227 #[allow(clippy::manual_async_fn)]
228 fn get_signature(
229 &self,
230 _pub_key_commitment: PublicKeyCommitment,
231 _signing_inputs: &SigningInputs,
232 ) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
233 async {
234 Err(AuthenticationError::RejectedSignature(
235 "default authenticator cannot provide signatures".to_string(),
236 ))
237 }
238 }
239}
240
241#[cfg(test)]
242mod test {
243 use miden_lib::utils::{Deserializable, Serializable};
244 use miden_objects::account::auth::AuthSecretKey;
245 use miden_objects::{Felt, Word};
246
247 use super::SigningInputs;
248
249 #[test]
250 fn serialize_auth_key() {
251 let auth_key = AuthSecretKey::new_rpo_falcon512();
252 let serialized = auth_key.to_bytes();
253 let deserialized = AuthSecretKey::read_from_bytes(&serialized).unwrap();
254
255 assert_eq!(auth_key, deserialized);
256 }
257
258 #[test]
259 fn serialize_deserialize_signing_inputs_arbitrary() {
260 let elements = vec![
261 Felt::new(0),
262 Felt::new(1),
263 Felt::new(2),
264 Felt::new(3),
265 Felt::new(4),
266 Felt::new(5),
267 Felt::new(6),
268 Felt::new(7),
269 ];
270 let inputs = SigningInputs::Arbitrary(elements.clone());
271 let bytes = inputs.to_bytes();
272 let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
273
274 match decoded {
275 SigningInputs::Arbitrary(decoded_elements) => {
276 assert_eq!(decoded_elements, elements);
277 },
278 _ => panic!("expected Arbitrary variant"),
279 }
280 }
281
282 #[test]
283 fn serialize_deserialize_signing_inputs_blind() {
284 let word = Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
285 let inputs = SigningInputs::Blind(word);
286 let bytes = inputs.to_bytes();
287 let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
288
289 match decoded {
290 SigningInputs::Blind(w) => {
291 assert_eq!(w, word);
292 },
293 _ => panic!("expected Blind variant"),
294 }
295 }
296}