miden_tx/auth/
tx_authenticator.rs

1use 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;
17
18// SIGNATURE DATA
19// ================================================================================================
20
21/// Data types on which a signature can be requested.
22///
23/// It supports three modes:
24/// - `TransactionSummary`: Structured transaction summary, recommended for authenticating
25///   transactions.
26/// - `Arbitrary`: Arbitrary payload provided by the application. It is up to the authenticator to
27///   display it appropriately.
28/// - `Blind`: The underlying data is not meant to be displayed in a human-readable format. It must
29///   be a cryptographic commitment to some data.
30#[derive(Debug, Clone)]
31pub enum SigningInputs {
32    TransactionSummary(Box<TransactionSummary>),
33    Arbitrary(Vec<Felt>),
34    Blind(Word),
35}
36
37impl SequentialCommit for SigningInputs {
38    type Commitment = Word;
39
40    fn to_elements(&self) -> Vec<Felt> {
41        match self {
42            SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_elements(),
43            SigningInputs::Arbitrary(elements) => elements.clone(),
44            SigningInputs::Blind(word) => word.as_elements().to_vec(),
45        }
46    }
47
48    fn to_commitment(&self) -> Self::Commitment {
49        match self {
50            // `TransactionSummary` knows how to derive a commitment to itself.
51            SigningInputs::TransactionSummary(tx_summary) => tx_summary.as_ref().to_commitment(),
52            // use the default implementation.
53            SigningInputs::Arbitrary(elements) => Hasher::hash_elements(elements),
54            // `Blind` is assumed to already be a commitment.
55            SigningInputs::Blind(word) => *word,
56        }
57    }
58}
59
60/// Convenience methods for [SigningInputs].
61impl SigningInputs {
62    /// Computes the commitment to [SigningInputs].
63    pub fn to_commitment(&self) -> Word {
64        <Self as SequentialCommit>::to_commitment(self)
65    }
66
67    /// Returns a representation of the [SigningInputs] as a sequence of field elements.
68    pub fn to_elements(&self) -> Vec<Felt> {
69        <Self as SequentialCommit>::to_elements(self)
70    }
71}
72
73// TRANSACTION AUTHENTICATOR
74// ================================================================================================
75
76/// Defines an authenticator for transactions.
77///
78/// The main purpose of the authenticator is to generate signatures for a given message against
79/// a key managed by the authenticator. That is, the authenticator maintains a set of public-
80/// private key pairs, and can be requested to generate signatures against any of the managed keys.
81///
82/// The public keys are defined by [Word]'s which are the hashes of the actual public keys.
83pub trait TransactionAuthenticator {
84    /// Retrieves a signature for a specific message as a list of [Felt].
85    ///
86    /// The request is initiated by the VM as a consequence of the SigToStack advice
87    /// injector.
88    ///
89    /// - `pub_key_hash`: The hash of the public key used for signature generation.
90    /// - `message`: The message to sign, usually a commitment to the transaction data.
91    /// - `account_delta`: An informational parameter describing the changes made to the account up
92    ///   to the point of calling `get_signature()`. This allows the authenticator to review any
93    ///   alterations to the account prior to signing. It should not be directly used in the
94    ///   signature computation.
95    fn get_signature(
96        &self,
97        pub_key: Word,
98        signing_inputs: &SigningInputs,
99    ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>>;
100}
101
102/// A placeholder type for the generic trait bound of `TransactionAuthenticator<'_,'_,_,T>`
103/// when we do not want to provide one, but must provide the `T` in `Option<T>`.
104///
105/// Note: Asserts when `get_signature` is called.
106#[derive(Debug, Clone, Copy)]
107pub struct UnreachableAuth {
108    // ensure the type cannot be instantiated
109    _protect: core::marker::PhantomData<u8>,
110}
111
112impl TransactionAuthenticator for UnreachableAuth {
113    #[allow(clippy::manual_async_fn)]
114    fn get_signature(
115        &self,
116        _pub_key: Word,
117        _signing_inputs: &SigningInputs,
118    ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>> {
119        async { unreachable!("Type `UnreachableAuth` must not be instantiated") }
120    }
121}
122
123// BASIC AUTHENTICATOR
124// ================================================================================================
125
126/// Represents a signer for [AuthSecretKey] keys.
127#[derive(Clone, Debug)]
128pub struct BasicAuthenticator<R> {
129    /// pub_key |-> secret_key mapping
130    keys: BTreeMap<Word, AuthSecretKey>,
131    rng: Arc<RwLock<R>>,
132}
133
134impl<R: Rng> BasicAuthenticator<R> {
135    #[cfg(feature = "std")]
136    pub fn new(keys: &[(Word, AuthSecretKey)]) -> BasicAuthenticator<rand::rngs::StdRng> {
137        use rand::SeedableRng;
138        use rand::rngs::StdRng;
139
140        let rng = StdRng::from_os_rng();
141        BasicAuthenticator::<StdRng>::new_with_rng(keys, rng)
142    }
143
144    pub fn new_with_rng(keys: &[(Word, AuthSecretKey)], rng: R) -> Self {
145        let mut key_map = BTreeMap::new();
146        for (word, secret_key) in keys {
147            key_map.insert(*word, secret_key.clone());
148        }
149
150        BasicAuthenticator {
151            keys: key_map,
152            rng: Arc::new(RwLock::new(rng)),
153        }
154    }
155
156    /// Returns a reference to the keys map. Map keys represent the public keys, and values
157    /// represent the secret keys that the authenticator would use to sign messages.
158    pub fn keys(&self) -> &BTreeMap<Word, AuthSecretKey> {
159        &self.keys
160    }
161}
162
163impl<R: Rng + Send + Sync> TransactionAuthenticator for BasicAuthenticator<R> {
164    /// Gets a signature over a message, given a public key.
165    /// The key should be included in the `keys` map and should be a variant of [AuthSecretKey].
166    ///
167    /// Supported signature schemes:
168    /// - RpoFalcon512
169    ///
170    /// # Errors
171    /// If the public key is not contained in the `keys` map,
172    /// [`AuthenticationError::UnknownPublicKey`] is returned.
173    fn get_signature(
174        &self,
175        pub_key: Word,
176        signing_inputs: &SigningInputs,
177    ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>> {
178        let message = signing_inputs.to_commitment();
179
180        async move {
181            let mut rng = self.rng.write().await;
182            match self.keys.get(&pub_key) {
183                Some(key) => match key {
184                    AuthSecretKey::RpoFalcon512(falcon_key) => {
185                        get_falcon_signature(falcon_key, message, &mut *rng)
186                    },
187                },
188                None => Err(AuthenticationError::UnknownPublicKey(format!(
189                    "public key {pub_key} is not contained in the authenticator's keys",
190                ))),
191            }
192        }
193    }
194}
195
196// HELPER FUNCTIONS
197// ================================================================================================
198
199impl TransactionAuthenticator for () {
200    #[allow(clippy::manual_async_fn)]
201    fn get_signature(
202        &self,
203        _pub_key: Word,
204        _signing_inputs: &SigningInputs,
205    ) -> impl FutureMaybeSend<Result<Vec<Felt>, AuthenticationError>> {
206        async {
207            Err(AuthenticationError::RejectedSignature(
208                "default authenticator cannot provide signatures".to_string(),
209            ))
210        }
211    }
212}
213
214#[cfg(test)]
215mod test {
216    use miden_lib::utils::{Deserializable, Serializable};
217    use miden_objects::account::AuthSecretKey;
218    use miden_objects::crypto::dsa::rpo_falcon512::SecretKey;
219
220    #[test]
221    fn serialize_auth_key() {
222        let secret_key = SecretKey::new();
223        let auth_key = AuthSecretKey::RpoFalcon512(secret_key.clone());
224        let serialized = auth_key.to_bytes();
225        let deserialized = AuthSecretKey::read_from_bytes(&serialized).unwrap();
226
227        match deserialized {
228            AuthSecretKey::RpoFalcon512(key) => assert_eq!(secret_key.to_bytes(), key.to_bytes()),
229        }
230    }
231}