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(
207 &self,
208 pub_key_commitment: PublicKeyCommitment,
209 signing_inputs: &SigningInputs,
210 ) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
211 let message = signing_inputs.to_commitment();
212
213 async move {
214 match self.keys.get(&pub_key_commitment) {
215 Some(key) => Ok(key.sign(message)),
216 None => Err(AuthenticationError::UnknownPublicKey(pub_key_commitment)),
217 }
218 }
219 }
220}
221
222impl TransactionAuthenticator for () {
226 #[allow(clippy::manual_async_fn)]
227 fn get_signature(
228 &self,
229 _pub_key_commitment: PublicKeyCommitment,
230 _signing_inputs: &SigningInputs,
231 ) -> impl FutureMaybeSend<Result<Signature, AuthenticationError>> {
232 async {
233 Err(AuthenticationError::RejectedSignature(
234 "default authenticator cannot provide signatures".to_string(),
235 ))
236 }
237 }
238}
239
240#[cfg(test)]
241mod test {
242 use miden_lib::utils::{Deserializable, Serializable};
243 use miden_objects::account::auth::AuthSecretKey;
244 use miden_objects::{Felt, Word};
245
246 use super::SigningInputs;
247
248 #[test]
249 fn serialize_auth_key() {
250 let auth_key = AuthSecretKey::new_rpo_falcon512();
251 let serialized = auth_key.to_bytes();
252 let deserialized = AuthSecretKey::read_from_bytes(&serialized).unwrap();
253
254 assert_eq!(auth_key, deserialized);
255 }
256
257 #[test]
258 fn serialize_deserialize_signing_inputs_arbitrary() {
259 let elements = vec![
260 Felt::new(0),
261 Felt::new(1),
262 Felt::new(2),
263 Felt::new(3),
264 Felt::new(4),
265 Felt::new(5),
266 Felt::new(6),
267 Felt::new(7),
268 ];
269 let inputs = SigningInputs::Arbitrary(elements.clone());
270 let bytes = inputs.to_bytes();
271 let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
272
273 match decoded {
274 SigningInputs::Arbitrary(decoded_elements) => {
275 assert_eq!(decoded_elements, elements);
276 },
277 _ => panic!("expected Arbitrary variant"),
278 }
279 }
280
281 #[test]
282 fn serialize_deserialize_signing_inputs_blind() {
283 let word = Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
284 let inputs = SigningInputs::Blind(word);
285 let bytes = inputs.to_bytes();
286 let decoded = SigningInputs::read_from_bytes(&bytes).unwrap();
287
288 match decoded {
289 SigningInputs::Blind(w) => {
290 assert_eq!(w, word);
291 },
292 _ => panic!("expected Blind variant"),
293 }
294 }
295}