Skip to main content

miden_standards/account/auth/
tx_fee_collector.rs

1use miden_protocol::account::auth::{AuthScheme, PublicKey};
2use miden_protocol::account::component::{
3    AccountComponentCode,
4    AccountComponentMetadata,
5    SchemaType,
6    StorageSchema,
7    StorageSlotSchema,
8};
9use miden_protocol::account::{
10    AccountComponent,
11    AccountComponentName,
12    AccountId,
13    StorageSlot,
14    StorageSlotName,
15};
16use miden_protocol::crypto::dsa::{ecdsa_k256_keccak, falcon512_poseidon2};
17use miden_protocol::note::{NoteTag, NoteType};
18use miden_protocol::utils::sync::LazyLock;
19use miden_protocol::{Felt, Hasher, Word};
20
21use super::Approver;
22use crate::account::account_component_code;
23
24account_component_code!(AUTH_TX_FEE_COLLECTOR_CODE, "miden-standards-auth-tx-fee-collector.masp");
25
26// CONSTANTS
27// ================================================================================================
28
29static PUBKEY_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
30    StorageSlotName::new("miden::standards::auth::tx_fee_collector::pub_key")
31        .expect("storage slot name should be valid")
32});
33
34static SIGNATURE_SCHEME_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
35    StorageSlotName::new("miden::standards::auth::tx_fee_collector::signature_scheme")
36        .expect("storage slot name should be valid")
37});
38
39/// An [`AccountComponent`] implementing the authentication scheme of a TX_FEE collector account: an
40/// account that forwards the assets of the notes it consumes and is never changed by a
41/// transaction.
42///
43/// It exports the procedure `auth_tx_fee_collector`, which:
44/// - Creates a P2ID note for the target given by the auth args (see [`Self::auth_args`]) and moves
45///   the single asset of every consumed note into it, straight from the note; the account's vault
46///   is never touched. A transaction consuming no notes is rejected, except for the one creating
47///   the account, which then creates no P2ID note.
48/// - Asserts the account's commitment is the one it had at the start of the transaction.
49/// - Never increments the nonce, except once when the account is created. That transaction is
50///   checked against the vault instead of the full commitment, so the account cannot be created
51///   holding assets.
52/// - Verifies a signature over the transaction summary against the public key in storage, under the
53///   signature scheme stored alongside it.
54/// - Creates no TX_FEE note.
55///
56/// The account is stateless after deployment, and transactions against it can be built
57/// concurrently. A batch builder collects the TX_FEE notes of many batches this way: each
58/// transaction consumes one batch's fee notes and forwards their assets to the builder's own
59/// account.
60///
61/// Replay protection comes from the input notes the signed transaction summary binds: a
62/// transaction leaving the account unchanged must consume at least one input note to be valid, and
63/// a note can be consumed once.
64///
65/// Like every auth component, it is installed alongside at least one other component, typically
66/// [`BasicWallet`](crate::account::wallets::BasicWallet).
67pub struct AuthTxFeeCollector {
68    approver: Approver,
69}
70
71impl AuthTxFeeCollector {
72    /// The name of the component.
73    pub const NAME: &'static str = "miden::standards::auth::tx_fee_collector";
74
75    /// Returns the canonical [`AccountComponentName`] of this component.
76    pub const fn name() -> AccountComponentName {
77        AccountComponentName::from_static_str(Self::NAME)
78    }
79
80    /// Returns the [`AccountComponentCode`] of this component.
81    pub fn code() -> &'static AccountComponentCode {
82        &AUTH_TX_FEE_COLLECTOR_CODE
83    }
84
85    /// Creates a new [`AuthTxFeeCollector`] component with the given approver.
86    pub fn new(approver: Approver) -> Self {
87        Self { approver }
88    }
89
90    /// Creates a new [`AuthTxFeeCollector`] component using the Falcon512Poseidon2 signature
91    /// scheme.
92    ///
93    /// The public key commitment is derived from the provided Falcon512 public key.
94    pub fn falcon512_poseidon2(pub_key: falcon512_poseidon2::PublicKey) -> Self {
95        Self {
96            approver: Approver::new(pub_key.into(), AuthScheme::Falcon512Poseidon2),
97        }
98    }
99
100    /// Creates a new [`AuthTxFeeCollector`] component using the EcdsaK256Keccak signature scheme.
101    ///
102    /// The public key commitment is derived from the provided ECDSA K256 public key.
103    ///
104    /// # Privacy
105    /// This scheme discloses the signer's public key and signature at proving time and
106    /// therefore does not provide public-key privacy. See
107    /// [`AuthScheme::EcdsaK256Keccak`][scheme] for details, and prefer
108    /// [`falcon512_poseidon2`](Self::falcon512_poseidon2) if signer-key privacy is required.
109    ///
110    /// [scheme]: miden_protocol::account::auth::AuthScheme::EcdsaK256Keccak
111    pub fn ecdsa_k256_keccak(pub_key: ecdsa_k256_keccak::PublicKey) -> Self {
112        Self {
113            approver: Approver::new(pub_key.into(), AuthScheme::EcdsaK256Keccak),
114        }
115    }
116
117    /// Creates a new [`AuthTxFeeCollector`] component from a [`PublicKey`].
118    ///
119    /// The authentication scheme and public key commitment are derived from the provided key.
120    pub fn from_public_key(pub_key: PublicKey) -> Self {
121        Self {
122            approver: Approver::new(pub_key.to_commitment(), pub_key.auth_scheme()),
123        }
124    }
125
126    /// Returns the approver of this component.
127    pub fn approver(&self) -> Approver {
128        self.approver
129    }
130
131    /// Returns the auth args that make the auth procedure create its P2ID note for `target`.
132    ///
133    /// The word is `[target_id_suffix, target_id_prefix, tag, note_type]`, with the tag derived
134    /// from `target` as by [`NoteTag::with_account_target`]. Pass it as the transaction's auth
135    /// args.
136    pub fn auth_args(target: AccountId, note_type: NoteType) -> Word {
137        Word::new([
138            target.suffix(),
139            target.prefix().as_felt(),
140            Felt::from(NoteTag::with_account_target(target)),
141            Felt::from(note_type),
142        ])
143    }
144
145    /// Derives the serial number of the P2ID note the auth procedure creates in a transaction
146    /// with the given auth args and input notes commitment.
147    ///
148    /// The serial number is `hash(auth_args || input_notes_commitment)`, which is unique per
149    /// transaction since no two valid transactions consume the same notes. Together with the
150    /// target it lets the note's recipient be computed before the transaction executes.
151    ///
152    /// This derivation must be kept in sync with `forward_note_assets` in the component's MASM
153    /// code.
154    pub fn derive_serial_number(auth_args: Word, input_notes_commitment: Word) -> Word {
155        Hasher::merge(&[auth_args, input_notes_commitment])
156    }
157
158    /// Returns the [`StorageSlotName`] where the public key is stored.
159    pub fn public_key_slot() -> &'static StorageSlotName {
160        &PUBKEY_SLOT_NAME
161    }
162
163    /// Returns the [`StorageSlotName`] where the signature scheme is stored.
164    pub fn signature_scheme_slot() -> &'static StorageSlotName {
165        &SIGNATURE_SCHEME_SLOT_NAME
166    }
167
168    /// Returns the storage slot schema for the public key slot.
169    pub fn public_key_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
170        (
171            Self::public_key_slot().clone(),
172            StorageSlotSchema::value("Public key commitment", SchemaType::pub_key()),
173        )
174    }
175
176    /// Returns the storage slot schema for the signature scheme slot.
177    pub fn signature_scheme_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
178        (
179            Self::signature_scheme_slot().clone(),
180            StorageSlotSchema::value("Signature scheme", SchemaType::auth_scheme()),
181        )
182    }
183
184    /// Returns the [`AccountComponentMetadata`] for this component.
185    pub fn component_metadata() -> AccountComponentMetadata {
186        let storage_schema = StorageSchema::new(vec![
187            Self::public_key_slot_schema(),
188            Self::signature_scheme_slot_schema(),
189        ])
190        .expect("storage schema should be valid");
191
192        AccountComponentMetadata::new(Self::NAME)
193            .with_description("TX_FEE collector authentication component")
194            .with_storage_schema(storage_schema)
195    }
196}
197
198impl From<AuthTxFeeCollector> for AccountComponent {
199    fn from(collector: AuthTxFeeCollector) -> Self {
200        let metadata = AuthTxFeeCollector::component_metadata();
201
202        let storage_slots = vec![
203            StorageSlot::with_value(
204                AuthTxFeeCollector::public_key_slot().clone(),
205                collector.approver.pub_key().into(),
206            ),
207            StorageSlot::with_value(
208                AuthTxFeeCollector::signature_scheme_slot().clone(),
209                Word::from([collector.approver.auth_scheme().as_u8(), 0, 0, 0]),
210            ),
211        ];
212
213        AccountComponent::new(AuthTxFeeCollector::code().clone(), storage_slots, metadata).expect(
214            "AuthTxFeeCollector component should satisfy the requirements of a valid account \
215             component",
216        )
217    }
218}