miden_client/account/mod.rs
1//! The `account` module provides types and client APIs for managing accounts within the Miden
2//! network.
3//!
4//! Accounts are foundational entities of the Miden protocol. They store assets and define
5//! rules for manipulating them. Once an account is registered with the client, its state will
6//! be updated accordingly, and validated against the network state on every sync.
7//!
8//! # Example
9//!
10//! To add a new account to the client's store, you might use the [`Client::add_account`] method as
11//! follows:
12//!
13//! ```rust
14//! # use miden_client::{
15//! # account::{Account, AccountBuilder, AccountBuilderSchemaCommitmentExt, AccountType, component::BasicWallet},
16//! # crypto::FeltRng
17//! # };
18//! # async fn add_new_account_example<AUTH>(
19//! # client: &mut miden_client::Client<AUTH>
20//! # ) -> Result<(), miden_client::ClientError> {
21//! # let random_seed = Default::default();
22//! let account = AccountBuilder::new(random_seed)
23//! .account_type(AccountType::Private)
24//! .with_component(BasicWallet)
25//! .build_with_schema_commitment()?;
26//!
27//! // Add the account to the client. The account already embeds its seed information.
28//! client.add_account(&account, false).await?;
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! For more details on accounts, refer to the [Account] documentation.
34
35use alloc::string::{String, ToString};
36use alloc::vec::Vec;
37
38use miden_protocol::Felt;
39use miden_protocol::account::auth::PublicKey;
40pub use miden_protocol::account::delta::AccountUpdateDetails;
41pub use miden_protocol::account::{
42 Account,
43 AccountBuilder,
44 AccountCode,
45 AccountComponent,
46 AccountComponentCode,
47 AccountDelta,
48 AccountFile,
49 AccountHeader,
50 AccountId,
51 AccountIdPrefix,
52 AccountIdPrefixV1,
53 AccountIdV1,
54 AccountIdVersion,
55 AccountProcedureRoot,
56 AccountStorage,
57 AccountType,
58 PartialAccount,
59 PartialStorage,
60 PartialStorageMap,
61 RoleSymbol,
62 StorageMap,
63 StorageMapDelta,
64 StorageMapKey,
65 StorageMapKeyHash,
66 StorageMapWitness,
67 StorageSlot,
68 StorageSlotContent,
69 StorageSlotDelta,
70 StorageSlotId,
71 StorageSlotName,
72 StorageSlotType,
73};
74pub use miden_protocol::address::{Address, AddressInterface, AddressType, NetworkId};
75use miden_protocol::asset::AssetVault;
76pub use miden_protocol::errors::{AccountIdError, AddressError, NetworkIdError};
77use miden_protocol::note::NoteTag;
78use miden_tx::utils::serde::{
79 ByteReader,
80 ByteWriter,
81 Deserializable,
82 DeserializationError,
83 Serializable,
84};
85
86/// Display-only metadata for a faucet account, persisted in the client's settings store.
87///
88/// Populated lazily by the CLI resolver from the on-chain token config of a public faucet
89/// and persisted under a `faucet_metadata:<faucet-id>` key.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct FaucetMetadata {
92 pub symbol: String,
93 pub decimals: u8,
94}
95
96impl Serializable for FaucetMetadata {
97 fn write_into<W: ByteWriter>(&self, target: &mut W) {
98 self.symbol.write_into(target);
99 target.write_u8(self.decimals);
100 }
101}
102
103impl Deserializable for FaucetMetadata {
104 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
105 let symbol = String::read_from(source)?;
106 let decimals = source.read_u8()?;
107 Ok(Self { symbol, decimals })
108 }
109}
110
111mod account_reader;
112pub use account_reader::AccountReader;
113/// Raw access to `miden-standards` account modules for items not curated by `miden-client`.
114pub use miden_standards::account as standards;
115use miden_standards::account::auth::AuthSingleSig;
116use miden_standards::account::faucets::FungibleFaucet;
117// RE-EXPORTS
118// ================================================================================================
119pub use miden_standards::account::interface::{
120 AccountComponentInterface,
121 AccountComponentInterfaceExt,
122 AccountInterface,
123 AccountInterfaceExt,
124};
125pub use miden_standards::account::metadata::{
126 AccountBuilderSchemaCommitmentExt,
127 AccountSchemaCommitment,
128};
129use miden_standards::account::wallets::BasicWallet;
130
131use super::Client;
132use crate::asset::TokenSymbol;
133use crate::errors::ClientError;
134use crate::rpc::domain::account::GetAccountRequest;
135use crate::rpc::node::{EndpointError, GetAccountError};
136use crate::store::{AccountStatus, AccountStorageFilter, ClientAccountType};
137use crate::sync::NoteTagRecord;
138
139pub mod component {
140 pub const MIDEN_PACKAGE_EXTENSION: &str = "masp";
141
142 pub use miden_protocol::account::auth::*;
143 pub use miden_protocol::account::component::{
144 FeltSchema,
145 InitStorageData,
146 InitStorageDataError,
147 MapSlotSchema,
148 SchemaRequirement,
149 SchemaType,
150 SchemaTypeError,
151 StorageSchema,
152 StorageSlotSchema,
153 StorageValueName,
154 StorageValueNameError,
155 ValueSlotSchema,
156 WordSchema,
157 WordValue,
158 };
159 pub use miden_protocol::account::{
160 AccountComponent,
161 AccountComponentMetadata,
162 AccountComponentName,
163 AccountProcedureRoot,
164 RoleSymbol,
165 };
166 pub use miden_standards::account::access::{
167 AccessControl,
168 Authority,
169 AuthorityError,
170 Ownable2Step,
171 Ownable2StepError,
172 Pausable,
173 PausableManager,
174 PausableStorage,
175 RoleBasedAccessControl,
176 };
177 pub use miden_standards::account::auth::*;
178 pub use miden_standards::account::components::StandardAccountComponent;
179 pub use miden_standards::account::faucets::{
180 Description,
181 ExternalLink,
182 FungibleFaucet,
183 FungibleFaucetBuilder,
184 FungibleFaucetError,
185 LogoURI,
186 TokenMetadata,
187 TokenMetadataError,
188 TokenName,
189 create_fungible_faucet,
190 };
191 pub use miden_standards::account::policies::{
192 AllowlistOwnerControlled,
193 AllowlistStorage,
194 BasicAllowlist,
195 BasicBlocklist,
196 BlocklistOwnerControlled,
197 BlocklistStorage,
198 BurnAllowAll,
199 BurnOwnerOnly,
200 BurnPolicyConfig,
201 MintAllowAll,
202 MintOwnerOnly,
203 MintPolicyConfig,
204 PolicyRegistration,
205 TokenPolicyManager,
206 TokenPolicyManagerError,
207 TransferAllowAll,
208 TransferPolicy,
209 };
210 pub use miden_standards::account::wallets::BasicWallet;
211}
212
213// CLIENT METHODS
214// ================================================================================================
215
216/// This section of the [Client] contains methods for:
217///
218/// - **Account creation:** Use the [`AccountBuilder`] to construct new accounts, specifying account
219/// visibility (`AccountType::Public` / `AccountType::Private`) and attaching necessary components
220/// (e.g., basic wallet or fungible faucet). Prefer
221/// [`AccountBuilderSchemaCommitmentExt::build_with_schema_commitment`] so the account includes
222/// merged storage schema commitment metadata; use plain [`AccountBuilder::build`] only when you
223/// need to opt out. After creation, accounts can be added to the client.
224///
225/// - **Account tracking:** Accounts added via the client are persisted to the local store, where
226/// their state (including nonce, balance, and metadata) is updated upon every synchronization
227/// with the network.
228///
229/// - **Data retrieval:** The module also provides methods to fetch account-related data.
230impl<AUTH> Client<AUTH> {
231 // ACCOUNT CREATION
232 // --------------------------------------------------------------------------------------------
233
234 /// Adds the provided [Account] in the store so it can start being tracked by the client.
235 ///
236 /// If the account is already being tracked and `overwrite` is set to `true`, the account will
237 /// be overwritten. Newly created accounts must embed their seed (`account.seed()` must return
238 /// `Some(_)`).
239 ///
240 /// # Errors
241 ///
242 /// - If the account is new but it does not contain the seed.
243 /// - If the account is already tracked and `overwrite` is set to `false`.
244 /// - If `overwrite` is set to `true` and the `account_data` nonce is lower than the one already
245 /// being tracked.
246 /// - If `overwrite` is set to `true` and the `account_data` commitment doesn't match the
247 /// network's account commitment.
248 pub async fn add_account(
249 &mut self,
250 account: &Account,
251 overwrite: bool,
252 ) -> Result<(), ClientError> {
253 self.add_account_inner(account, ClientAccountType::Native, overwrite).await
254 }
255
256 /// Inserts `account` into the store (or overwrites it if `overwrite` is true) and registers
257 /// the per-account note tag if `client_account_type` is [`ClientAccountType::Native`].
258 ///
259 /// Switching the [`ClientAccountType`] of an already-tracked account is not supported and
260 /// returns [`ClientError::AccountWatchedMismatch`].
261 async fn add_account_inner(
262 &mut self,
263 account: &Account,
264 client_account_type: ClientAccountType,
265 overwrite: bool,
266 ) -> Result<(), ClientError> {
267 if account.is_new() {
268 if account.seed().is_none() {
269 return Err(ClientError::AddNewAccountWithoutSeed);
270 }
271 } else {
272 // Ignore the seed since it's not a new account
273 if account.seed().is_some() {
274 tracing::warn!(
275 "Added an existing account and still provided a seed when it is not needed. It's possible that the account's file was incorrectly generated. The seed will be ignored."
276 );
277 }
278 }
279
280 let tracked_account = self.store.get_account(account.id()).await?;
281
282 match tracked_account {
283 None => {
284 let default_address = Address::new(account.id());
285
286 self.store
287 .insert_account(account, default_address.clone(), client_account_type)
288 .await
289 .map_err(ClientError::StoreError)?;
290
291 if matches!(client_account_type, ClientAccountType::Native) {
292 // Set the default address note tag so sync pulls notes.
293 let default_address_note_tag = default_address.to_note_tag();
294 let note_tag_record =
295 NoteTagRecord::with_account_source(default_address_note_tag, account.id());
296 self.store.add_note_tag(note_tag_record).await?;
297 }
298
299 Ok(())
300 },
301 Some(tracked_account) => {
302 if !overwrite {
303 // Only overwrite the account if the flag is set to `true`
304 return Err(ClientError::AccountAlreadyTracked(account.id()));
305 }
306
307 if client_account_type != tracked_account.client_account_type() {
308 // Switching between Watched and Native after the account is tracked is not
309 // supported: the per-account note tag and any client-side state derived from
310 // that mode are set up at insertion time and not migrated on the fly.
311 return Err(ClientError::AccountWatchedMismatch(account.id()));
312 }
313
314 if tracked_account.nonce().as_canonical_u64() > account.nonce().as_canonical_u64() {
315 // If the new account is older than the one being tracked, return an error
316 return Err(ClientError::AccountNonceTooLow);
317 }
318
319 if tracked_account.is_locked() {
320 // If the tracked account is locked, check that the account commitment matches
321 // the one in the network
322 let network_account_commitment = self
323 .rpc_api
324 .get_account(account.id(), GetAccountRequest::new())
325 .await?
326 .1
327 .account_commitment();
328 if network_account_commitment != account.to_commitment() {
329 return Err(ClientError::AccountCommitmentMismatch(
330 network_account_commitment,
331 ));
332 }
333 }
334
335 self.store.update_account(account).await?;
336
337 Ok(())
338 },
339 }
340 }
341
342 /// Imports an account from the network to the client's store. The account needs to be public
343 /// and be tracked by the network, it will be fetched by its ID. If the account was already
344 /// being tracked by the client, its state will be overwritten.
345 ///
346 /// To import an account as watched (state-tracking only, no note sync), use
347 /// [`Self::import_watched_account_by_id`] instead. Switching an already-tracked account
348 /// between Native and Watched is not supported.
349 ///
350 /// # Errors
351 /// - If the account is not found on the network.
352 /// - If the account is private.
353 /// - If the account is already tracked as watched.
354 /// - There was an error sending the request to the network.
355 pub async fn import_account_by_id(&mut self, account_id: AccountId) -> Result<(), ClientError> {
356 let account = self.fetch_public_account(account_id).await?;
357 self.add_account_inner(&account, ClientAccountType::Native, true).await
358 }
359
360 /// Starts watching an on-chain account ([`ClientAccountType::Watched`]).
361 ///
362 /// Like [`Self::import_account_by_id`], the account is fetched from the network by its ID.
363 /// Unlike `import_account_by_id`, the account is added without registering its derived note
364 /// tag: `sync_state` will keep the account's commitment, nonce and storage up to date but
365 /// will **not** pull notes targeted at it.
366 ///
367 /// If the account is already being tracked as watched its state is overwritten. Switching an
368 /// already-tracked native account to watched is not supported.
369 ///
370 /// # Errors
371 /// - If the account is not found on the network.
372 /// - If the account is private.
373 /// - If the account is already tracked as native.
374 /// - There was an error sending the request to the network.
375 pub async fn import_watched_account_by_id(
376 &mut self,
377 account_id: AccountId,
378 ) -> Result<(), ClientError> {
379 let account = self.fetch_public_account(account_id).await?;
380 self.add_account_inner(&account, ClientAccountType::Watched, true).await
381 }
382
383 /// Fetches a public [`Account`] from the network, returning a typed error when the account
384 /// doesn't exist on chain or is private.
385 async fn fetch_public_account(&self, account_id: AccountId) -> Result<Account, ClientError> {
386 let fetched_account =
387 self.rpc_api.get_account_details(account_id).await.map_err(|err| {
388 match err.endpoint_error() {
389 Some(EndpointError::GetAccount(GetAccountError::AccountNotFound)) => {
390 ClientError::AccountNotFoundOnChain(account_id)
391 },
392 _ => ClientError::RpcError(err),
393 }
394 })?;
395
396 fetched_account.ok_or(ClientError::AccountIsPrivate(account_id))
397 }
398
399 /// Fetches a public faucet's display metadata from the network.
400 ///
401 /// Uses [`get_account`](crate::rpc::NodeRpcClient::get_account) with a minimal request so that
402 /// the node does not return vault data. The faucet's token config lives in a single value slot,
403 /// which is always present in the returned storage header.
404 ///
405 /// Returns:
406 /// - `Ok(Some(_))` — the account is public and its token config storage slot decoded.
407 /// - `Ok(None)` — the account is private, not on chain, or the storage slot does not parse
408 /// as a token config. Caller should fall back to a raw display.
409 /// - `Err(_)` — transport-level RPC error.
410 pub async fn fetch_remote_token_metadata(
411 &self,
412 faucet_id: AccountId,
413 ) -> Result<Option<FaucetMetadata>, ClientError> {
414 let proof = match self.rpc_api.get_account(faucet_id, GetAccountRequest::new()).await {
415 Ok((_, proof)) => proof,
416 Err(err) => match err.endpoint_error() {
417 Some(EndpointError::GetAccount(
418 GetAccountError::AccountNotFound | GetAccountError::AccountNotPublic,
419 )) => return Ok(None),
420 _ => return Err(ClientError::RpcError(err)),
421 },
422 };
423
424 let Some(storage_header) = proof.storage_header() else {
425 return Ok(None);
426 };
427
428 let Some(slot_header) =
429 storage_header.find_slot_header_by_name(FungibleFaucet::token_config_slot())
430 else {
431 return Ok(None);
432 };
433
434 let [_token_supply, _max_supply, decimals, symbol] = *slot_header.value();
435 let Ok(symbol) = TokenSymbol::try_from(symbol) else {
436 return Ok(None);
437 };
438 let Ok(decimals) = u8::try_from(decimals.as_canonical_u64()) else {
439 return Ok(None);
440 };
441 Ok(Some(FaucetMetadata { symbol: symbol.to_string(), decimals }))
442 }
443
444 /// Adds an [`Address`] to the associated [`AccountId`], alongside its derived [`NoteTag`]. If
445 /// the account is tracked as watched, the note tag is not registered.
446 ///
447 /// # Errors
448 /// - If the account is not found on the network.
449 /// - If the address is already being tracked.
450 pub async fn add_address(
451 &mut self,
452 address: Address,
453 account_id: AccountId,
454 ) -> Result<(), ClientError> {
455 let network_id = self.rpc_api.get_network_id().await?;
456 let address_bench32 = address.encode(network_id);
457 if self.store.get_addresses_by_account_id(account_id).await?.contains(&address) {
458 return Err(ClientError::AddressAlreadyTracked(address_bench32));
459 }
460
461 let tracked_account = self.store.get_account(account_id).await?;
462 match tracked_account {
463 None => Err(ClientError::AccountDataNotFound(account_id)),
464 Some(tracked_account) => {
465 self.store.insert_address(address.clone(), account_id).await?;
466 // Watched accounts intentionally have no derived note tag registered to avoid sync
467 // state pulling notes for them.
468 if !tracked_account.is_watched() {
469 let derived_note_tag: NoteTag = address.to_note_tag();
470 let note_tag_record =
471 NoteTagRecord::with_account_source(derived_note_tag, account_id);
472 self.store.add_note_tag(note_tag_record).await?;
473 }
474 Ok(())
475 },
476 }
477 }
478
479 /// Removes an [`Address`] from the associated [`AccountId`], alongside its derived [`NoteTag`].
480 /// If no address was tracked for the given account, this is a no-op.
481 pub async fn remove_address(
482 &mut self,
483 address: Address,
484 account_id: AccountId,
485 ) -> Result<(), ClientError> {
486 let derived_note_tag = address.to_note_tag();
487 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
488 self.store.remove_address(address).await?;
489 // Remove the note tag if no other address are associated with it.
490 let addresses = self.store.get_addresses_by_account_id(account_id).await?;
491 if addresses.iter().all(|address| address.to_note_tag() != derived_note_tag) {
492 self.store.remove_note_tag(note_tag_record).await?;
493 }
494 Ok(())
495 }
496
497 // ACCOUNT DATA RETRIEVAL
498 // --------------------------------------------------------------------------------------------
499
500 /// Retrieves the asset vault for a specific account.
501 ///
502 /// To check the balance for a single asset, use [`Client::account_reader`] instead.
503 pub async fn get_account_vault(
504 &self,
505 account_id: AccountId,
506 ) -> Result<AssetVault, ClientError> {
507 self.store.get_account_vault(account_id).await.map_err(ClientError::StoreError)
508 }
509
510 /// Retrieves the whole account storage for a specific account.
511 ///
512 /// To only load a specific slot, use [`Client::account_reader`] instead.
513 pub async fn get_account_storage(
514 &self,
515 account_id: AccountId,
516 ) -> Result<AccountStorage, ClientError> {
517 self.store
518 .get_account_storage(account_id, AccountStorageFilter::All)
519 .await
520 .map_err(ClientError::StoreError)
521 }
522
523 /// Retrieves the account code for a specific account.
524 ///
525 /// Returns `None` if the account is not found.
526 pub async fn get_account_code(
527 &self,
528 account_id: AccountId,
529 ) -> Result<Option<AccountCode>, ClientError> {
530 self.store.get_account_code(account_id).await.map_err(ClientError::StoreError)
531 }
532
533 /// Returns a list of [`AccountHeader`] of all accounts stored in the database along with their
534 /// statuses.
535 ///
536 /// Said accounts' state is the state after the last performed sync.
537 pub async fn get_account_headers(
538 &self,
539 ) -> Result<Vec<(AccountHeader, AccountStatus)>, ClientError> {
540 self.store.get_account_headers().await.map_err(Into::into)
541 }
542
543 /// Retrieves the full [`Account`] object from the store, returning `None` if not found.
544 ///
545 /// This method loads the complete account state including vault, storage, and code.
546 ///
547 /// For lazy access that fetches only the data you need, use
548 /// [`Client::account_reader`] instead.
549 ///
550 /// Use [`Client::try_get_account`] if you want to error when the account is not found.
551 pub async fn get_account(&self, account_id: AccountId) -> Result<Option<Account>, ClientError> {
552 match self.store.get_account(account_id).await? {
553 Some(record) => Ok(Some(record.try_into()?)),
554 None => Ok(None),
555 }
556 }
557
558 /// Retrieves the full [`Account`] object from the store, erroring if not found.
559 ///
560 /// This method loads the complete account state including vault, storage, and code.
561 ///
562 /// Use [`Client::get_account`] if you want to handle missing accounts gracefully.
563 pub async fn try_get_account(&self, account_id: AccountId) -> Result<Account, ClientError> {
564 self.get_account(account_id)
565 .await?
566 .ok_or(ClientError::AccountDataNotFound(account_id))
567 }
568
569 /// Creates an [`AccountReader`] for lazy access to account data.
570 ///
571 /// The `AccountReader` provides lazy access to account state - each method call
572 /// fetches fresh data from storage, ensuring you always see the current state.
573 ///
574 /// For loading the full [`Account`] object, use [`Client::get_account`] instead.
575 ///
576 /// # Example
577 /// ```ignore
578 /// let reader = client.account_reader(account_id);
579 ///
580 /// // Each call fetches fresh data
581 /// let nonce = reader.nonce().await?;
582 /// let balance = reader.get_balance(faucet_id).await?;
583 ///
584 /// // Storage access is integrated
585 /// let value = reader.get_storage_item("my_slot").await?;
586 /// let (map_value, witness) = reader.get_storage_map_witness("balances", key).await?;
587 /// ```
588 pub fn account_reader(&self, account_id: AccountId) -> AccountReader {
589 AccountReader::new(self.store.clone(), account_id)
590 }
591
592 /// Prunes historical account states for the specified account up to the given nonce.
593 ///
594 /// Deletes all historical entries with `replaced_at_nonce <= up_to_nonce` and any
595 /// orphaned account code.
596 ///
597 /// Returns the total number of rows deleted, including historical entries and orphaned
598 /// account code.
599 pub async fn prune_account_history(
600 &self,
601 account_id: AccountId,
602 up_to_nonce: Felt,
603 ) -> Result<usize, ClientError> {
604 Ok(self.store.prune_account_history(account_id, up_to_nonce).await?)
605 }
606}
607
608// UTILITY FUNCTIONS
609// ================================================================================================
610
611/// Builds an regular account ID from the provided parameters. The ID may be used along
612/// `Client::import_account_by_id` to import a public account from the network (provided that the
613/// used seed is known).
614///
615/// This function currently supports accounts composed of the [`BasicWallet`] component and one of
616/// the supported authentication schemes ([`AuthSingleSig`]).
617///
618/// # Arguments
619/// - `init_seed`: Initial seed used to create the account. This is the seed passed to
620/// [`AccountBuilder::new`].
621/// - `public_key`: Public key of the account used for the authentication component.
622/// - `account_visibility`: Public/private visibility of the account.
623///
624/// # Errors
625/// - If the account cannot be built.
626pub fn build_wallet_id(
627 init_seed: [u8; 32],
628 public_key: &PublicKey,
629 account_visibility: AccountType,
630) -> Result<AccountId, ClientError> {
631 let auth_scheme = public_key.auth_scheme();
632 let auth_component: AccountComponent =
633 AuthSingleSig::new(public_key.to_commitment(), auth_scheme).into();
634
635 let account = AccountBuilder::new(init_seed)
636 .account_type(account_visibility)
637 .with_auth_component(auth_component)
638 .with_component(BasicWallet)
639 .build_with_schema_commitment()?;
640
641 Ok(account.id())
642}
643
644#[cfg(test)]
645mod schema_commitment_tests {
646 use miden_protocol::EMPTY_WORD;
647 use miden_protocol::account::auth::AuthSecretKey;
648 use miden_standards::account::metadata::AccountSchemaCommitment;
649
650 use super::{
651 AccountBuilder,
652 AccountBuilderSchemaCommitmentExt,
653 AccountType,
654 AuthSingleSig,
655 BasicWallet,
656 };
657 use crate::auth::AuthSchemeId;
658
659 #[test]
660 fn wallet_build_includes_schema_commitment_metadata_slot() {
661 let key = AuthSecretKey::new_falcon512_poseidon2();
662 let account = AccountBuilder::new([2u8; 32])
663 .account_type(AccountType::Private)
664 .with_auth_component(AuthSingleSig::new(
665 key.public_key().to_commitment(),
666 AuthSchemeId::Falcon512Poseidon2,
667 ))
668 .with_component(BasicWallet)
669 .build_with_schema_commitment()
670 .expect("build_with_schema_commitment");
671
672 let commitment = account
673 .storage()
674 .get_item(AccountSchemaCommitment::schema_commitment_slot())
675 .expect("schema commitment slot");
676 assert_ne!(commitment, EMPTY_WORD);
677 }
678}