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 a Falcon512Poseidon2 secret key using the provided cryptographic random number
145    /// generator.
146    pub fn new_falcon512_poseidon2_with_rng<R: Rng + CryptoRng>(rng: &mut R) -> Self {
147        Self::Falcon512Poseidon2(falcon512_poseidon2::SecretKey::with_rng::<R>(rng))
148    }
149
150    /// Generates an EcdsaK256Keccak secret key from the OS-provided randomness.
151    ///
152    /// Note: the EcdsaK256Keccak scheme discloses the signer's public key and signature at proving
153    /// time and therefore does not provide public-key privacy. See
154    /// [`AuthScheme::EcdsaK256Keccak`] for details.
155    #[cfg(feature = "std")]
156    pub fn new_ecdsa_k256_keccak() -> Self {
157        Self::EcdsaK256Keccak(ecdsa_k256_keccak::SigningKey::new())
158    }
159
160    /// Generates an EcdsaK256Keccak secret key using the provided random number generator.
161    ///
162    /// Note: the EcdsaK256Keccak scheme discloses the signer's public key and signature at proving
163    /// time and therefore does not provide public-key privacy. See
164    /// [`AuthScheme::EcdsaK256Keccak`] for details.
165    pub fn new_ecdsa_k256_keccak_with_rng<R: Rng + CryptoRng>(rng: &mut R) -> Self {
166        Self::EcdsaK256Keccak(ecdsa_k256_keccak::SigningKey::with_rng::<R>(rng))
167    }
168
169    /// Generates a new secret key for the specified authentication scheme using the provided
170    /// random number generator.
171    ///
172    /// Returns an error if the specified authentication scheme is not supported.
173    pub fn with_scheme_and_rng<R: Rng + CryptoRng>(
174        scheme: AuthScheme,
175        rng: &mut R,
176    ) -> Result<Self, AuthSchemeError> {
177        match scheme {
178            AuthScheme::Falcon512Poseidon2 => Ok(Self::new_falcon512_poseidon2_with_rng(rng)),
179            AuthScheme::EcdsaK256Keccak => Ok(Self::new_ecdsa_k256_keccak_with_rng(rng)),
180        }
181    }
182
183    /// Generates a new secret key for the specified authentication scheme from the
184    /// OS-provided randomness.
185    ///
186    /// Returns an error if the specified authentication scheme is not supported.
187    #[cfg(feature = "std")]
188    pub fn with_scheme(scheme: AuthScheme) -> Result<Self, AuthSchemeError> {
189        match scheme {
190            AuthScheme::Falcon512Poseidon2 => Ok(Self::new_falcon512_poseidon2()),
191            AuthScheme::EcdsaK256Keccak => Ok(Self::new_ecdsa_k256_keccak()),
192        }
193    }
194
195    /// Returns the authentication scheme of this secret key.
196    pub fn auth_scheme(&self) -> AuthScheme {
197        match self {
198            AuthSecretKey::Falcon512Poseidon2(_) => AuthScheme::Falcon512Poseidon2,
199            AuthSecretKey::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak,
200        }
201    }
202
203    /// Returns a public key associated with this secret key.
204    pub fn public_key(&self) -> PublicKey {
205        match self {
206            AuthSecretKey::Falcon512Poseidon2(key) => {
207                PublicKey::Falcon512Poseidon2(key.public_key())
208            },
209            AuthSecretKey::EcdsaK256Keccak(key) => PublicKey::EcdsaK256Keccak(key.public_key()),
210        }
211    }
212
213    /// Signs the provided message with this secret key.
214    pub fn sign(&self, message: Word) -> Signature {
215        match self {
216            AuthSecretKey::Falcon512Poseidon2(key) => {
217                Signature::Falcon512Poseidon2(key.sign(message))
218            },
219            AuthSecretKey::EcdsaK256Keccak(key) => Signature::EcdsaK256Keccak(key.sign(message)),
220        }
221    }
222}
223
224impl Serializable for AuthSecretKey {
225    fn write_into<W: ByteWriter>(&self, target: &mut W) {
226        self.auth_scheme().write_into(target);
227        match self {
228            AuthSecretKey::Falcon512Poseidon2(key) => key.write_into(target),
229            AuthSecretKey::EcdsaK256Keccak(key) => key.write_into(target),
230        }
231    }
232}
233
234impl Deserializable for AuthSecretKey {
235    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
236        match source.read::<AuthScheme>()? {
237            AuthScheme::Falcon512Poseidon2 => {
238                let secret_key = falcon512_poseidon2::SecretKey::read_from(source)?;
239                Ok(AuthSecretKey::Falcon512Poseidon2(secret_key))
240            },
241            AuthScheme::EcdsaK256Keccak => {
242                let secret_key = ecdsa_k256_keccak::SigningKey::read_from(source)?;
243                Ok(AuthSecretKey::EcdsaK256Keccak(secret_key))
244            },
245        }
246    }
247}
248
249// PUBLIC KEY
250// ================================================================================================
251
252/// Commitment to a public key.
253#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
254pub struct PublicKeyCommitment(Word);
255
256impl core::fmt::Display for PublicKeyCommitment {
257    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
258        write!(f, "{}", self.0)
259    }
260}
261
262impl From<falcon512_poseidon2::PublicKey> for PublicKeyCommitment {
263    fn from(value: falcon512_poseidon2::PublicKey) -> Self {
264        Self(value.to_commitment())
265    }
266}
267
268impl From<ecdsa_k256_keccak::PublicKey> for PublicKeyCommitment {
269    fn from(value: ecdsa_k256_keccak::PublicKey) -> Self {
270        Self(value.to_commitment())
271    }
272}
273
274impl From<PublicKeyCommitment> for Word {
275    fn from(value: PublicKeyCommitment) -> Self {
276        value.0
277    }
278}
279
280impl From<Word> for PublicKeyCommitment {
281    fn from(value: Word) -> Self {
282        Self(value)
283    }
284}
285
286/// Public keys of the standard authentication schemes available in the Miden protocol.
287#[derive(Clone, Debug)]
288#[non_exhaustive]
289pub enum PublicKey {
290    Falcon512Poseidon2(falcon512_poseidon2::PublicKey),
291    EcdsaK256Keccak(ecdsa_k256_keccak::PublicKey),
292}
293
294impl PublicKey {
295    /// Returns the authentication scheme of this public key.
296    pub fn auth_scheme(&self) -> AuthScheme {
297        match self {
298            PublicKey::Falcon512Poseidon2(_) => AuthScheme::Falcon512Poseidon2,
299            PublicKey::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak,
300        }
301    }
302
303    /// Returns a commitment to this public key.
304    pub fn to_commitment(&self) -> PublicKeyCommitment {
305        match self {
306            PublicKey::Falcon512Poseidon2(key) => key.to_commitment().into(),
307            PublicKey::EcdsaK256Keccak(key) => key.to_commitment().into(),
308        }
309    }
310
311    /// Verifies the provided signature against the provided message and this public key.
312    pub fn verify(&self, message: Word, signature: Signature) -> bool {
313        match (self, signature) {
314            (PublicKey::Falcon512Poseidon2(key), Signature::Falcon512Poseidon2(sig)) => {
315                key.verify(message, &sig)
316            },
317            (PublicKey::EcdsaK256Keccak(key), Signature::EcdsaK256Keccak(sig)) => {
318                key.verify(message, &sig)
319            },
320            _ => false,
321        }
322    }
323}
324
325impl Serializable for PublicKey {
326    fn write_into<W: ByteWriter>(&self, target: &mut W) {
327        self.auth_scheme().write_into(target);
328        match self {
329            PublicKey::Falcon512Poseidon2(pub_key) => pub_key.write_into(target),
330            PublicKey::EcdsaK256Keccak(pub_key) => pub_key.write_into(target),
331        }
332    }
333}
334
335impl Deserializable for PublicKey {
336    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
337        match source.read::<AuthScheme>()? {
338            AuthScheme::Falcon512Poseidon2 => {
339                let pub_key = falcon512_poseidon2::PublicKey::read_from(source)?;
340                Ok(PublicKey::Falcon512Poseidon2(pub_key))
341            },
342            AuthScheme::EcdsaK256Keccak => {
343                let pub_key = ecdsa_k256_keccak::PublicKey::read_from(source)?;
344                Ok(PublicKey::EcdsaK256Keccak(pub_key))
345            },
346        }
347    }
348}
349
350// SIGNATURE
351// ================================================================================================
352
353/// Represents a signature object ready for native verification.
354///
355/// In order to use this signature within the Miden VM, an encoding step may be necessary to
356/// convert the native signature into a vector of field elements that can be loaded into the advice
357/// provider. To encode the signature, use the provided [`Signature::to_encoded_signature`] method:
358/// ```rust,no_run
359/// use miden_protocol::account::auth::Signature;
360/// use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey;
361/// use miden_protocol::{Felt, Word};
362///
363/// let secret_key = SecretKey::new();
364/// let message = Word::default();
365/// let signature: Signature = secret_key.sign(message).into();
366/// let encoded_signature: Vec<Felt> = signature.to_encoded_signature(message);
367/// ```
368#[derive(Clone, Debug)]
369#[repr(u8)]
370pub enum Signature {
371    Falcon512Poseidon2(falcon512_poseidon2::Signature) = FALCON512_POSEIDON2,
372    EcdsaK256Keccak(ecdsa_k256_keccak::Signature) = ECDSA_K256_KECCAK,
373}
374
375impl Signature {
376    /// The maximum number of field elements that [`Signature::to_encoded_signature`] can return
377    /// for any variant.
378    ///
379    /// This lets consumers reject an encoded signature of an impossible length before allocating
380    /// for it. The largest encoding is the one of [`Signature::Falcon512Poseidon2`].
381    pub const MAX_NUM_ENCODED_SIGNATURE_FELTS: usize = 2058;
382
383    /// Returns the authentication scheme of this signature.
384    pub fn auth_scheme(&self) -> AuthScheme {
385        match self {
386            Signature::Falcon512Poseidon2(_) => AuthScheme::Falcon512Poseidon2,
387            Signature::EcdsaK256Keccak(_) => AuthScheme::EcdsaK256Keccak,
388        }
389    }
390
391    /// Converts this signature to a sequence of field elements in the format expected by the
392    /// native verification procedure in the VM.
393    ///
394    /// The returned vector contains at most [`Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS`]
395    /// elements.
396    ///
397    /// The order of elements in the returned vector is reversed because it is expected that the
398    /// data will be pushed into the advice stack
399    pub fn to_encoded_signature(&self, msg: Word) -> Vec<Felt> {
400        // TODO: the `expect()` should be changed to an error; but that will be a part of a bigger
401        // refactoring
402        match self {
403            Signature::Falcon512Poseidon2(sig) => {
404                miden_core_lib::dsa::falcon512_poseidon2::encode_signature(sig.public_key(), sig)
405            },
406            Signature::EcdsaK256Keccak(sig) => {
407                let pk = ecdsa_k256_keccak::PublicKey::recover_from(msg, sig)
408                    .expect("inferring public key from signature and message should succeed");
409                miden_core_lib::dsa::ecdsa_k256_keccak::encode_signature(&pk, sig)
410            },
411        }
412    }
413}
414
415impl From<falcon512_poseidon2::Signature> for Signature {
416    fn from(signature: falcon512_poseidon2::Signature) -> Self {
417        Signature::Falcon512Poseidon2(signature)
418    }
419}
420
421impl Serializable for Signature {
422    fn write_into<W: ByteWriter>(&self, target: &mut W) {
423        self.auth_scheme().write_into(target);
424        match self {
425            Signature::Falcon512Poseidon2(signature) => signature.write_into(target),
426            Signature::EcdsaK256Keccak(signature) => signature.write_into(target),
427        }
428    }
429}
430
431impl Deserializable for Signature {
432    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
433        match source.read::<AuthScheme>()? {
434            AuthScheme::Falcon512Poseidon2 => {
435                let signature = falcon512_poseidon2::Signature::read_from(source)?;
436                Ok(Signature::Falcon512Poseidon2(signature))
437            },
438            AuthScheme::EcdsaK256Keccak => {
439                let signature = ecdsa_k256_keccak::Signature::read_from(source)?;
440                Ok(Signature::EcdsaK256Keccak(signature))
441            },
442        }
443    }
444}
445
446// TESTS
447// ================================================================================================
448
449#[cfg(test)]
450mod tests {
451    use rand::SeedableRng;
452    use rand_chacha::ChaCha20Rng;
453    use rstest::rstest;
454
455    use super::*;
456
457    /// The encoding of every [`Signature`] variant must fit within
458    /// [`Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS`], which consumers rely on to bound the
459    /// untrusted encoded signatures they accept.
460    #[rstest]
461    #[case::falcon512_poseidon2(AuthScheme::Falcon512Poseidon2, 2058)]
462    #[case::ecdsa_k256_keccak(AuthScheme::EcdsaK256Keccak, 32)]
463    fn encoded_signature_does_not_exceed_max_num_felts(
464        #[case] auth_scheme: AuthScheme,
465        #[case] expected_num_felts: usize,
466    ) -> anyhow::Result<()> {
467        let mut rng = ChaCha20Rng::from_seed([0; 32]);
468        let secret_key = AuthSecretKey::with_scheme_and_rng(auth_scheme, &mut rng)?;
469        let message = Word::from([1u32, 2, 3, 4]);
470        let signature = secret_key.sign(message);
471
472        let encoded_signature = signature.to_encoded_signature(message);
473        assert_eq!(encoded_signature.len(), expected_num_felts);
474        assert!(encoded_signature.len() <= Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS);
475
476        // Match exhaustively on `Signature` so adding a variant breaks compilation as a reminder
477        // to add a case above and to re-check the maximum.
478        match signature {
479            Signature::Falcon512Poseidon2(_) | Signature::EcdsaK256Keccak(_) => {},
480        }
481
482        Ok(())
483    }
484}