miden_objects/account/
file.rs

1#[cfg(feature = "std")]
2use std::{
3    fs::{self, File},
4    io::{self, Read},
5    path::Path,
6    vec::Vec,
7};
8
9use miden_crypto::utils::SliceReader;
10
11use super::{
12    super::utils::serde::{
13        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
14    },
15    Account, AuthSecretKey, Word,
16};
17
18const MAGIC: &str = "acct";
19
20// ACCOUNT FILE
21// ================================================================================================
22
23/// Account file contains a complete description of an account, including the [Account] struct as
24/// well as account seed and account authentication info.
25///
26/// The intent of this struct is to provide an easy way to serialize and deserialize all
27/// account-related data as a single unit (e.g., to/from files).
28#[derive(Debug, Clone)]
29pub struct AccountFile {
30    pub account: Account,
31    pub account_seed: Option<Word>,
32    pub auth_secret_key: AuthSecretKey,
33}
34
35impl AccountFile {
36    pub fn new(account: Account, account_seed: Option<Word>, auth: AuthSecretKey) -> Self {
37        Self {
38            account,
39            account_seed,
40            auth_secret_key: auth,
41        }
42    }
43}
44
45#[cfg(feature = "std")]
46impl AccountFile {
47    /// Serializes and writes binary [AccountFile] to specified file
48    pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
49        fs::write(filepath, self.to_bytes())
50    }
51
52    /// Reads from file and tries to deserialize an [AccountFile]
53    pub fn read(filepath: impl AsRef<Path>) -> io::Result<Self> {
54        let mut file = File::open(filepath)?;
55        let mut buffer = Vec::new();
56
57        file.read_to_end(&mut buffer)?;
58        let mut reader = SliceReader::new(&buffer);
59
60        Ok(AccountFile::read_from(&mut reader).map_err(|_| io::ErrorKind::InvalidData)?)
61    }
62}
63
64// SERIALIZATION
65// ================================================================================================
66
67impl Serializable for AccountFile {
68    fn write_into<W: ByteWriter>(&self, target: &mut W) {
69        target.write_bytes(MAGIC.as_bytes());
70        let AccountFile {
71            account,
72            account_seed,
73            auth_secret_key: auth,
74        } = self;
75
76        account.write_into(target);
77        account_seed.write_into(target);
78        auth.write_into(target);
79    }
80}
81
82impl Deserializable for AccountFile {
83    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
84        let magic_value = source.read_string(4)?;
85        if magic_value != MAGIC {
86            return Err(DeserializationError::InvalidValue(format!(
87                "invalid account file marker: {magic_value}"
88            )));
89        }
90        let account = Account::read_from(source)?;
91        let account_seed = <Option<Word>>::read_from(source)?;
92        let auth_secret_key = AuthSecretKey::read_from(source)?;
93
94        Ok(Self::new(account, account_seed, auth_secret_key))
95    }
96
97    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
98        Self::read_from(&mut SliceReader::new(bytes))
99    }
100}
101
102// TESTS
103// ================================================================================================
104
105#[cfg(test)]
106mod tests {
107    use miden_crypto::{
108        dsa::rpo_falcon512::SecretKey,
109        utils::{Deserializable, Serializable},
110    };
111    use storage::AccountStorage;
112    #[cfg(feature = "std")]
113    use tempfile::tempdir;
114
115    use super::AccountFile;
116    use crate::{
117        account::{Account, AccountCode, AccountId, AuthSecretKey, Felt, Word, storage},
118        asset::AssetVault,
119        testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
120    };
121
122    fn build_account_file() -> AccountFile {
123        let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
124        let code = AccountCode::mock();
125
126        // create account and auth
127        let vault = AssetVault::new(&[]).unwrap();
128        let storage = AccountStorage::new(vec![]).unwrap();
129        let nonce = Felt::new(0);
130        let account = Account::from_parts(id, vault, storage, code, nonce);
131        let account_seed = Some(Word::default());
132        let auth_secret_key = AuthSecretKey::RpoFalcon512(SecretKey::new());
133
134        AccountFile::new(account, account_seed, auth_secret_key)
135    }
136
137    #[test]
138    fn test_serde() {
139        let account_file = build_account_file();
140        let serialized = account_file.to_bytes();
141        let deserialized = AccountFile::read_from_bytes(&serialized).unwrap();
142        assert_eq!(deserialized.account, account_file.account);
143        assert_eq!(deserialized.account_seed, account_file.account_seed);
144        assert_eq!(
145            deserialized.auth_secret_key.to_bytes(),
146            account_file.auth_secret_key.to_bytes()
147        );
148    }
149
150    #[cfg(feature = "std")]
151    #[test]
152    fn test_serde_file() {
153        let dir = tempdir().unwrap();
154        let filepath = dir.path().join("account_file.mac");
155
156        let account_file = build_account_file();
157        account_file.write(filepath.as_path()).unwrap();
158        let deserialized = AccountFile::read(filepath.as_path()).unwrap();
159
160        assert_eq!(deserialized.account, account_file.account);
161        assert_eq!(deserialized.account_seed, account_file.account_seed);
162        assert_eq!(
163            deserialized.auth_secret_key.to_bytes(),
164            account_file.auth_secret_key.to_bytes()
165        );
166    }
167}