1use std::fs;
10use std::io::Read;
11
12use wallet_core::Seed;
13
14use crate::crypto::decrypt;
15use crate::{Error, WalletPath};
16
17pub const OLD_MAGIC: u32 = 0x1d0c15;
19pub const MAGIC: u32 = 0x72736b;
21pub const LATEST_VERSION: Version = (0, 0, 1, 0, false);
23pub const FILE_TYPE: u16 = 0x0200;
25pub const RESERVED: u16 = 0x0000;
27type Version = (u8, u8, u8, u8, bool);
29
30#[derive(Copy, Clone, Debug, PartialEq)]
32pub enum DatFileVersion {
33 Legacy,
35 OldWalletCli(Version),
37 RuskBinaryFileFormat(Version),
39}
40
41impl DatFileVersion {
42 pub fn is_old(&self) -> bool {
45 matches!(self, Self::Legacy | Self::OldWalletCli(_))
46 }
47}
48
49pub(crate) fn get_seed_and_address(
51 file: DatFileVersion,
52 mut bytes: Vec<u8>,
53 pwd: &[u8],
54) -> Result<(Seed, u8), Error> {
55 match file {
56 DatFileVersion::Legacy => {
57 if bytes[1] == 0 && bytes[2] == 0 {
58 bytes.drain(..3);
59 }
60
61 bytes = decrypt(&bytes, pwd)?;
62
63 let seed = bytes[..]
65 .try_into()
66 .map_err(|_| Error::WalletFileCorrupted)?;
67
68 Ok((seed, 1))
69 }
70 DatFileVersion::OldWalletCli((major, minor, _, _, _)) => {
71 bytes.drain(..5);
72
73 let result: Result<(Seed, u8), Error> = match (major, minor) {
74 (1, 0) => {
75 let content = decrypt(&bytes, pwd)?;
76 let buff = &content[..];
77
78 let seed = buff
79 .try_into()
80 .map_err(|_| Error::WalletFileCorrupted)?;
81
82 Ok((seed, 1))
83 }
84 (2, 0) => {
85 let content = decrypt(&bytes, pwd)?;
86 let buff = &content[..];
87
88 let seed = buff
90 .try_into()
91 .map_err(|_| Error::WalletFileCorrupted)?;
92
93 Ok((seed, buff[0]))
95 }
96 _ => Err(Error::UnknownFileVersion(major, minor)),
97 };
98
99 result
100 }
101 DatFileVersion::RuskBinaryFileFormat(_) => {
102 let rest = bytes.get(12..(12 + 96));
103 if let Some(rest) = rest {
104 let content = decrypt(rest, pwd)?;
105
106 if let Some(seed_buff) = content.get(0..65) {
107 let seed = seed_buff[0..64]
108 .try_into()
109 .map_err(|_| Error::WalletFileCorrupted)?;
110
111 let count = &seed_buff[64..65];
112
113 Ok((seed, count[0]))
114 } else {
115 Err(Error::WalletFileCorrupted)
116 }
117 } else {
118 Err(Error::WalletFileCorrupted)
119 }
120 }
121 }
122}
123
124pub(crate) fn check_version(
128 bytes: Option<&[u8]>,
129) -> Result<DatFileVersion, Error> {
130 match bytes {
131 Some(bytes) => {
132 let header_bytes: [u8; 4] = bytes[0..4]
133 .try_into()
134 .map_err(|_| Error::WalletFileCorrupted)?;
135
136 let magic = u32::from_le_bytes(header_bytes) & 0x00ffffff;
137
138 if magic == OLD_MAGIC {
139 let (major, minor) = (bytes[3], bytes[4]);
141
142 Ok(DatFileVersion::OldWalletCli((major, minor, 0, 0, false)))
143 } else {
144 let header_bytes = bytes[0..8]
145 .try_into()
146 .map_err(|_| Error::WalletFileCorrupted)?;
147
148 let number = u64::from_be_bytes(header_bytes);
149
150 let magic_num = (number & 0xFFFFFF00000000) >> 32;
151
152 if (magic_num as u32) != MAGIC {
153 return Ok(DatFileVersion::Legacy);
154 }
155
156 let file_type = (number & 0x000000FFFF0000) >> 16;
157 let reserved = number & 0x0000000000FFFF;
158
159 if file_type != FILE_TYPE as u64 {
160 return Err(Error::WalletFileCorrupted);
161 };
162
163 if reserved != RESERVED as u64 {
164 return Err(Error::WalletFileCorrupted);
165 };
166
167 let version_bytes = bytes[8..12]
168 .try_into()
169 .map_err(|_| Error::WalletFileCorrupted)?;
170
171 let version = u32::from_be_bytes(version_bytes);
172
173 let major = (version & 0xff000000) >> 24;
174 let minor = (version & 0x00ff0000) >> 16;
175 let patch = (version & 0x0000ff00) >> 8;
176 let pre = (version & 0x000000f0) >> 4;
177 let higher = version & 0x0000000f;
178
179 let pre_higher = matches!(higher, 1);
180
181 Ok(DatFileVersion::RuskBinaryFileFormat((
182 major as u8,
183 minor as u8,
184 patch as u8,
185 pre as u8,
186 pre_higher,
187 )))
188 }
189 }
190 None => Err(Error::WalletFileCorrupted),
191 }
192}
193
194pub fn read_file_version(file: &WalletPath) -> Result<DatFileVersion, Error> {
197 let path = &file.wallet;
198
199 if !path.is_file() {
201 return Err(Error::WalletFileMissing);
202 }
203
204 let mut fs = fs::File::open(path)?;
205
206 let mut header_buf = [0; 12];
207
208 fs.read_exact(&mut header_buf)?;
209
210 check_version(Some(&header_buf))
211}
212
213pub(crate) fn version_bytes(version: Version) -> [u8; 4] {
214 u32::from_be_bytes([version.0, version.1, version.2, version.3])
215 .to_be_bytes()
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn distiction_between_versions() {
224 let old_wallet_file = vec![0x15, 0x0c, 0x1d, 0x02, 0x00];
226 let legacy_file = vec![
228 0xab, 0x38, 0x81, 0x3b, 0xfc, 0x79, 0x11, 0xf9, 0x86, 0xd6, 0xd0,
229 ];
230 let new_file = vec![
232 0x00, 0x72, 0x73, 0x6b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
233 0x00,
234 ];
235
236 assert_eq!(
237 check_version(Some(&old_wallet_file)).unwrap(),
238 DatFileVersion::OldWalletCli((2, 0, 0, 0, false))
239 );
240
241 assert_eq!(
242 check_version(Some(&legacy_file)).unwrap(),
243 DatFileVersion::Legacy
244 );
245
246 assert_eq!(
247 check_version(Some(&new_file)).unwrap(),
248 DatFileVersion::RuskBinaryFileFormat((0, 0, 1, 0, false))
249 );
250 }
251}