Skip to main content

miden_protocol/account/
auth.rs

1use alloc::borrow::ToOwned;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4use core::str::FromStr;
5
6use rand::{CryptoRng, Rng};
7
8use crate::crypto::dsa::{ecdsa_k256_keccak, falcon512_poseidon2};
9use crate::errors::AuthSchemeError;
10use crate::utils::serde::{
11    ByteReader,
12    ByteWriter,
13    Deserializable,
14    DeserializationError,
15    Serializable,
16};
17use crate::{Felt, Word};
18
19// AUTH SCHEME
20// ================================================================================================
21
22/// Identifier of signature schemes use for transaction authentication
23const FALCON512_POSEIDON2: u8 = 2;
24const ECDSA_K256_KECCAK: u8 = 1;
25
26const FALCON512_POSEIDON2_STR: &str = "Falcon512Poseidon2";
27const ECDSA_K256_KECCAK_STR: &str = "EcdsaK256Keccak";
28
29/// Defines standard authentication schemes (i.e., signature schemes) available in the Miden
30/// protocol.
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32#[non_exhaustive]
33#[repr(u8)]
34pub enum AuthScheme {
35    /// A deterministic Falcon512 signature scheme.
36    ///
37    /// This version differs from the reference Falcon512 implementation in its use of the poseidon2
38    /// hash function in its hash-to-point algorithm to make signatures very efficient to verify
39    /// inside Miden VM.
40    Falcon512Poseidon2 = FALCON512_POSEIDON2,
41
42    /// ECDSA signature scheme over secp256k1 curve using Keccak to hash the messages when signing.
43    ///
44    /// # Privacy
45    ///
46    /// Unlike [`Falcon512Poseidon2`](Self::Falcon512Poseidon2), which is verified entirely
47    /// in-circuit, this scheme is verified via a precompile. Under the current native
48    /// re-verification model, the precompile calldata - the raw 33-byte compressed secp256k1 public
49    /// key and the 65-byte signature - must be carried inside the transaction proof for it to
50    /// verify: the verifier recomputes the precompile transcript from that calldata and binds it
51    /// into the proof's public inputs, so it cannot be stripped or withheld. As a result the public
52    /// key and signature are disclosed to the node operator and to any party on the transaction
53    /// submission or gossip path, even though the account commits on-chain only to `Poseidon2(pk)`.
54    ///
55    /// This means `EcdsaK256Keccak` does not provide the public-key privacy that commitment-based
56    /// storage otherwise implies. Integrators requiring signer-key privacy should select
57    /// [`Falcon512Poseidon2`](Self::Falcon512Poseidon2) instead, which emits no public-key or
58    /// signature calldata.
59    EcdsaK256Keccak = ECDSA_K256_KECCAK,
60}
61
62impl AuthScheme {
63    /// Returns a numerical value of this auth scheme.
64    pub fn as_u8(&self) -> u8 {
65        *self as u8
66    }
67}
68
69impl core::fmt::Display for AuthScheme {
70    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71        match self {
72            Self::Falcon512Poseidon2 => f.write_str(FALCON512_POSEIDON2_STR),
73            Self::EcdsaK256Keccak => f.write_str(ECDSA_K256_KECCAK_STR),
74        }
75    }
76}
77
78impl TryFrom<u8> for AuthScheme {
79    type Error = AuthSchemeError;
80
81    fn try_from(value: u8) -> Result<Self, Self::Error> {
82        match value {
83            FALCON512_POSEIDON2 => Ok(Self::Falcon512Poseidon2),
84            ECDSA_K256_KECCAK => Ok(Self::EcdsaK256Keccak),
85            value => Err(AuthSchemeError::InvalidAuthSchemeIdentifier(value.to_string())),
86        }
87    }
88}
89
90impl FromStr for AuthScheme {
91    type Err = AuthSchemeError;
92
93    fn from_str(input: &str) -> Result<Self, Self::Err> {
94        match input {
95            FALCON512_POSEIDON2_STR => Ok(AuthScheme::Falcon512Poseidon2),
96            ECDSA_K256_KECCAK_STR => Ok(AuthScheme::EcdsaK256Keccak),
97            other => Err(AuthSchemeError::InvalidAuthSchemeIdentifier(other.to_owned())),
98        }
99    }
100}
101
102impl Serializable for AuthScheme {
103    fn write_into<W: ByteWriter>(&self, target: &mut W) {
104        target.write_u8(*self as u8);
105    }
106
107    fn get_size_hint(&self) -> usize {
108        // auth scheme is encoded as a single byte
109        size_of::<u8>()
110    }
111}
112
113impl Deserializable for AuthScheme {
114    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
115        match source.read_u8()? {
116            FALCON512_POSEIDON2 => Ok(Self::Falcon512Poseidon2),
117            ECDSA_K256_KECCAK => Ok(Self::EcdsaK256Keccak),
118            value => Err(DeserializationError::InvalidValue(format!(
119                "auth scheme identifier `{value}` is not valid"
120            ))),
121        }
122    }
123}
124
125// AUTH SECRET KEY
126// ================================================================================================
127
128/// Secret keys of the standard [`AuthScheme`]s available in the Miden protocol.
129#[derive(Clone, Debug, PartialEq, Eq)]
130#[non_exhaustive]
131#[repr(u8)]
132pub enum AuthSecretKey {
133    Falcon512Poseidon2(falcon512_poseidon2::SecretKey) = FALCON512_POSEIDON2,
134    EcdsaK256Keccak(ecdsa_k256_keccak::SigningKey) = ECDSA_K256_KECCAK,
135}
136
137impl AuthSecretKey {
138    /// Generates an Falcon512Poseidon2 secret key from the OS-provided randomness.
139    #[cfg(feature = "std")]
140    pub fn new_falcon512_poseidon2() -> Self {
141        Self::Falcon512Poseidon2(falcon512_poseidon2::SecretKey::new())
142    }
143
144    /// Generates an Falcon512Poseidon2 secrete key using the provided random number generator.
145    pub fn new_falcon512_poseidon2_with_rng<R: Rng>(rng: &mut R) -> Self {
146        Self::Falcon512Poseidon2(falcon512_poseidon2::SecretKey::with_rng::<R>(rng))
147    }
148
149    /// Generates an EcdsaK256Keccak secret key from the OS-provided randomness.
150    ///
151    /// Note: the EcdsaK256Keccak scheme discloses the signer's public key and signature at proving
152    /// time and therefore does not provide public-key privacy. See
153    /// [`AuthScheme::EcdsaK256Keccak`] for details.
154    #[cfg(feature = "std")]
155    pub fn new_ecdsa_k256_keccak() -> Self {
156        Self::EcdsaK256Keccak(ecdsa_k256_keccak::SigningKey::new())
157    }
158
159    /// Generates an EcdsaK256Keccak secret key using the provided random number generator.
160    ///
161    /// Note: the EcdsaK256Keccak scheme discloses the signer's public key and signature at proving
162    /// time and therefore does not provide public-key privacy. See
163    /// [`AuthScheme::EcdsaK256Keccak`] for details.
164    pub fn new_ecdsa_k256_keccak_with_rng<R: Rng + CryptoRng>(rng: &mut R) -> Self {
165        Self::EcdsaK256Keccak(ecdsa_k256_keccak::SigningKey::with_rng::<R>(rng))
166    }
167
168    /// Generates a new secret key for the specified authentication scheme using the provided
169    /// random number generator.
170    ///
171    /// Returns an error if the specified authentication scheme is not supported.
172    pub fn with_scheme_and_rng<R: Rng + CryptoRng>(
173        scheme: AuthScheme,
174        rng: &mut R,
175    ) -> Result<Self, AuthSchemeError> {
176        match scheme {
177            AuthScheme::Falcon512Poseidon2 => Ok(Self::new_falcon512_poseidon2_with_rng(rng)),
178            AuthScheme::EcdsaK256Keccak => Ok(Self::new_ecdsa_k256_keccak_with_rng(rng)),
179        }
180    }
181
182    /// Generates a new secret key for the specified authentication scheme from the
183    /// OS-provided randomness.
184    ///
185    /// Returns an error if the specified authentication scheme is not supported.
186    #[cfg(feature = "std")]
187    pub fn with_scheme(scheme: AuthScheme) -> Result<Self, AuthSchemeError> {
188        match scheme {
189            AuthScheme::Falcon512Poseidon2 => Ok(Self::new_falcon512_poseidon2()),
190            AuthScheme::EcdsaK256Keccak => Ok(Self::new_ecdsa_k256_keccak()),
191        }
192    }
193
194    /// Returns the authentication scheme of this secret key.
195    pub fn auth_scheme(&self) -> AuthScheme {
196        match self {
197            AuthSecretKey::Falcon512Poseidon2(_) => AuthScheme::Falcon512Poseidon2,
198            AuthSecretKey::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak,
199        }
200    }
201
202    /// Returns a public key associated with this secret key.
203    pub fn public_key(&self) -> PublicKey {
204        match self {
205            AuthSecretKey::Falcon512Poseidon2(key) => {
206                PublicKey::Falcon512Poseidon2(key.public_key())
207            },
208            AuthSecretKey::EcdsaK256Keccak(key) => PublicKey::EcdsaK256Keccak(key.public_key()),
209        }
210    }
211
212    /// Signs the provided message with this secret key.
213    pub fn sign(&self, message: Word) -> Signature {
214        match self {
215            AuthSecretKey::Falcon512Poseidon2(key) => {
216                Signature::Falcon512Poseidon2(key.sign(message))
217            },
218            AuthSecretKey::EcdsaK256Keccak(key) => Signature::EcdsaK256Keccak(key.sign(message)),
219        }
220    }
221}
222
223impl Serializable for AuthSecretKey {
224    fn write_into<W: ByteWriter>(&self, target: &mut W) {
225        self.auth_scheme().write_into(target);
226        match self {
227            AuthSecretKey::Falcon512Poseidon2(key) => key.write_into(target),
228            AuthSecretKey::EcdsaK256Keccak(key) => key.write_into(target),
229        }
230    }
231}
232
233impl Deserializable for AuthSecretKey {
234    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
235        match source.read::<AuthScheme>()? {
236            AuthScheme::Falcon512Poseidon2 => {
237                let secret_key = falcon512_poseidon2::SecretKey::read_from(source)?;
238                Ok(AuthSecretKey::Falcon512Poseidon2(secret_key))
239            },
240            AuthScheme::EcdsaK256Keccak => {
241                let secret_key = ecdsa_k256_keccak::SigningKey::read_from(source)?;
242                Ok(AuthSecretKey::EcdsaK256Keccak(secret_key))
243            },
244        }
245    }
246}
247
248// PUBLIC KEY
249// ================================================================================================
250
251/// Commitment to a public key.
252#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
253pub struct PublicKeyCommitment(Word);
254
255impl core::fmt::Display for PublicKeyCommitment {
256    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
257        write!(f, "{}", self.0)
258    }
259}
260
261impl From<falcon512_poseidon2::PublicKey> for PublicKeyCommitment {
262    fn from(value: falcon512_poseidon2::PublicKey) -> Self {
263        Self(value.to_commitment())
264    }
265}
266
267impl From<ecdsa_k256_keccak::PublicKey> for PublicKeyCommitment {
268    fn from(value: ecdsa_k256_keccak::PublicKey) -> Self {
269        Self(value.to_commitment())
270    }
271}
272
273impl From<PublicKeyCommitment> for Word {
274    fn from(value: PublicKeyCommitment) -> Self {
275        value.0
276    }
277}
278
279impl From<Word> for PublicKeyCommitment {
280    fn from(value: Word) -> Self {
281        Self(value)
282    }
283}
284
285/// Public keys of the standard authentication schemes available in the Miden protocol.
286#[derive(Clone, Debug)]
287#[non_exhaustive]
288pub enum PublicKey {
289    Falcon512Poseidon2(falcon512_poseidon2::PublicKey),
290    EcdsaK256Keccak(ecdsa_k256_keccak::PublicKey),
291}
292
293impl PublicKey {
294    /// Returns the authentication scheme of this public key.
295    pub fn auth_scheme(&self) -> AuthScheme {
296        match self {
297            PublicKey::Falcon512Poseidon2(_) => AuthScheme::Falcon512Poseidon2,
298            PublicKey::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak,
299        }
300    }
301
302    /// Returns a commitment to this public key.
303    pub fn to_commitment(&self) -> PublicKeyCommitment {
304        match self {
305            PublicKey::Falcon512Poseidon2(key) => key.to_commitment().into(),
306            PublicKey::EcdsaK256Keccak(key) => key.to_commitment().into(),
307        }
308    }
309
310    /// Verifies the provided signature against the provided message and this public key.
311    pub fn verify(&self, message: Word, signature: Signature) -> bool {
312        match (self, signature) {
313            (PublicKey::Falcon512Poseidon2(key), Signature::Falcon512Poseidon2(sig)) => {
314                key.verify(message, &sig)
315            },
316            (PublicKey::EcdsaK256Keccak(key), Signature::EcdsaK256Keccak(sig)) => {
317                key.verify(message, &sig)
318            },
319            _ => false,
320        }
321    }
322}
323
324impl Serializable for PublicKey {
325    fn write_into<W: ByteWriter>(&self, target: &mut W) {
326        self.auth_scheme().write_into(target);
327        match self {
328            PublicKey::Falcon512Poseidon2(pub_key) => pub_key.write_into(target),
329            PublicKey::EcdsaK256Keccak(pub_key) => pub_key.write_into(target),
330        }
331    }
332}
333
334impl Deserializable for PublicKey {
335    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
336        match source.read::<AuthScheme>()? {
337            AuthScheme::Falcon512Poseidon2 => {
338                let pub_key = falcon512_poseidon2::PublicKey::read_from(source)?;
339                Ok(PublicKey::Falcon512Poseidon2(pub_key))
340            },
341            AuthScheme::EcdsaK256Keccak => {
342                let pub_key = ecdsa_k256_keccak::PublicKey::read_from(source)?;
343                Ok(PublicKey::EcdsaK256Keccak(pub_key))
344            },
345        }
346    }
347}
348
349// SIGNATURE
350// ================================================================================================
351
352/// Represents a signature object ready for native verification.
353///
354/// In order to use this signature within the Miden VM, an encoding step may be necessary to
355/// convert the native signature into a vector of field elements that can be loaded into the advice
356/// provider. To encode the signature, use the provided [`Signature::to_encoded_signature`] method:
357/// ```rust,no_run
358/// use miden_protocol::account::auth::Signature;
359/// use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey;
360/// use miden_protocol::{Felt, Word};
361///
362/// let secret_key = SecretKey::new();
363/// let message = Word::default();
364/// let signature: Signature = secret_key.sign(message).into();
365/// let encoded_signature: Vec<Felt> = signature.to_encoded_signature(message);
366/// ```
367#[derive(Clone, Debug)]
368#[repr(u8)]
369pub enum Signature {
370    Falcon512Poseidon2(falcon512_poseidon2::Signature) = FALCON512_POSEIDON2,
371    EcdsaK256Keccak(ecdsa_k256_keccak::Signature) = ECDSA_K256_KECCAK,
372}
373
374impl Signature {
375    /// The maximum number of field elements that [`Signature::to_encoded_signature`] can return
376    /// for any variant.
377    ///
378    /// This lets consumers reject an encoded signature of an impossible length before allocating
379    /// for it. The largest encoding is the one of [`Signature::Falcon512Poseidon2`].
380    pub const MAX_NUM_ENCODED_SIGNATURE_FELTS: usize = 2058;
381
382    /// Returns the authentication scheme of this signature.
383    pub fn auth_scheme(&self) -> AuthScheme {
384        match self {
385            Signature::Falcon512Poseidon2(_) => AuthScheme::Falcon512Poseidon2,
386            Signature::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak,
387        }
388    }
389
390    /// Converts this signature to a sequence of field elements in the format expected by the
391    /// native verification procedure in the VM.
392    ///
393    /// The returned vector contains at most [`Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS`]
394    /// elements.
395    ///
396    /// The order of elements in the returned vector is reversed because it is expected that the
397    /// data will be pushed into the advice stack
398    pub fn to_encoded_signature(&self, msg: Word) -> Vec<Felt> {
399        // TODO: the `expect()` should be changed to an error; but that will be a part of a bigger
400        // refactoring
401        match self {
402            Signature::Falcon512Poseidon2(sig) => {
403                miden_core_lib::dsa::falcon512_poseidon2::encode_signature(sig.public_key(), sig)
404            },
405            Signature::EcdsaK256Keccak(sig) => {
406                let pk = ecdsa_k256_keccak::PublicKey::recover_from(msg, sig)
407                    .expect("inferring public key from signature and message should succeed");
408                miden_core_lib::dsa::ecdsa_k256_keccak::encode_signature(&pk, sig)
409            },
410        }
411    }
412}
413
414impl From<falcon512_poseidon2::Signature> for Signature {
415    fn from(signature: falcon512_poseidon2::Signature) -> Self {
416        Signature::Falcon512Poseidon2(signature)
417    }
418}
419
420impl Serializable for Signature {
421    fn write_into<W: ByteWriter>(&self, target: &mut W) {
422        self.auth_scheme().write_into(target);
423        match self {
424            Signature::Falcon512Poseidon2(signature) => signature.write_into(target),
425            Signature::EcdsaK256Keccak(signature) => signature.write_into(target),
426        }
427    }
428}
429
430impl Deserializable for Signature {
431    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
432        match source.read::<AuthScheme>()? {
433            AuthScheme::Falcon512Poseidon2 => {
434                let signature = falcon512_poseidon2::Signature::read_from(source)?;
435                Ok(Signature::Falcon512Poseidon2(signature))
436            },
437            AuthScheme::EcdsaK256Keccak => {
438                let signature = ecdsa_k256_keccak::Signature::read_from(source)?;
439                Ok(Signature::EcdsaK256Keccak(signature))
440            },
441        }
442    }
443}
444
445// TESTS
446// ================================================================================================
447
448#[cfg(test)]
449mod tests {
450    use rand::SeedableRng;
451    use rand_chacha::ChaCha20Rng;
452    use rstest::rstest;
453
454    use super::*;
455
456    /// The encoding of every [`Signature`] variant must fit within
457    /// [`Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS`], which consumers rely on to bound the
458    /// untrusted encoded signatures they accept.
459    #[rstest]
460    #[case::falcon512_poseidon2(AuthScheme::Falcon512Poseidon2, 2058)]
461    #[case::ecdsa_k256_keccak(AuthScheme::EcdsaK256Keccak, 32)]
462    fn encoded_signature_does_not_exceed_max_num_felts(
463        #[case] auth_scheme: AuthScheme,
464        #[case] expected_num_felts: usize,
465    ) -> anyhow::Result<()> {
466        let mut rng = ChaCha20Rng::from_seed([0; 32]);
467        let secret_key = AuthSecretKey::with_scheme_and_rng(auth_scheme, &mut rng)?;
468        let message = Word::from([1u32, 2, 3, 4]);
469        let signature = secret_key.sign(message);
470
471        let encoded_signature = signature.to_encoded_signature(message);
472        assert_eq!(encoded_signature.len(), expected_num_felts);
473        assert!(encoded_signature.len() <= Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS);
474
475        // Match exhaustively on `Signature` so adding a variant breaks compilation as a reminder
476        // to add a case above and to re-check the maximum.
477        match signature {
478            Signature::Falcon512Poseidon2(_) | Signature::EcdsaK256Keccak(_) => {},
479        }
480
481        Ok(())
482    }
483}