1#![no_std]
11extern crate alloc;
12
13use constants::*;
14use crypto::{decrypt, encrypt};
15use error::{Result, WalletDataFileError as Error};
16
17use seed::Seed;
18
19use alloc::vec::Vec;
20
21mod crypto;
22mod error;
23mod seed;
24mod constants {
25 pub const MAGIC: u32 = 0x72736b;
27 pub const OLD_MAGIC: u32 = 0x1d0c15;
29 pub const FILE_TYPE: u16 = 0x0200;
30 pub const RESERVED: u16 = 0x0000;
31 pub const LATEST_VERSION: super::Version = (0, 0, 2, 0, false);
32}
33
34type Version = (u8, u8, u8, u8, bool);
35
36#[derive(Copy, Clone, Debug, PartialEq)]
38pub enum DatFileVersion {
39 Legacy,
41 OldWalletCli(Version),
43 RuskBinaryFileFormat(Version),
45}
46
47impl DatFileVersion {
48 pub fn is_old(&self) -> bool {
51 matches!(self, Self::Legacy | Self::OldWalletCli(_))
52 }
53}
54
55pub fn get_seed_and_address(
57 file: DatFileVersion,
58 mut bytes: Vec<u8>,
59 pwd: &str,
60) -> Result<(Seed, u64)> {
61 let pwd = blake3::hash(pwd.as_bytes());
62 let pwd = pwd.as_bytes();
63
64 match file {
65 DatFileVersion::Legacy => {
66 if bytes[1] == 0 && bytes[2] == 0 {
67 bytes.drain(..3);
68 }
69
70 bytes = decrypt(&bytes, pwd)?;
71
72 let seed = Seed::from_reader(&bytes[..]).map_err(|_| Error::WalletFileCorrupted)?;
74
75 Ok((seed, 1))
76 }
77 DatFileVersion::OldWalletCli((major, minor, _, _, _)) => {
78 bytes.drain(..5);
79
80 let result: Result<(Seed, _)> = match (major, minor) {
81 (1, 0) => {
82 let content = decrypt(&bytes, pwd)?;
83 let buff = &content[..];
84
85 let seed = Seed::from_reader(buff).map_err(|_| Error::WalletFileCorrupted)?;
86
87 Ok((seed, 0))
88 }
89 (2, 0) => {
90 let content = decrypt(&bytes, pwd)?;
91 let buff = &content[..];
92
93 let seed = Seed::from_reader(buff).map_err(|_| Error::WalletFileCorrupted)?;
95
96 Ok((seed, 0))
98 }
99 _ => Err(Error::UnknownFileVersion(major, minor)),
100 };
101
102 result
103 }
104 DatFileVersion::RuskBinaryFileFormat(version) => {
105 let rest = bytes.get(12..(12 + 96));
106
107 if let Some(rest) = rest {
108 let content = decrypt(rest, pwd)?;
109
110 if let Some(seed_buff) = content.get(0..65) {
111 let seed = Seed::from_reader(&seed_buff[0..64])
113 .map_err(|_| Error::WalletFileCorrupted)?;
114
115 match version {
116 (0, 0, 2, 0, false) => {
117 if let Some(last_pos_bytes) = content.get(64..72) {
118 let last_pos = match last_pos_bytes.try_into() {
119 Ok(last_pos_bytes) => u64::from_le_bytes(last_pos_bytes),
120 Err(_) => return Err(Error::NoLastPosFound),
121 };
122
123 Ok((seed, last_pos))
124 } else {
125 Err(Error::WalletFileCorrupted)
126 }
127 }
128 _ => Ok((seed, 0)),
129 }
130 } else {
131 Err(Error::WalletFileCorrupted)
132 }
133 } else {
134 Err(Error::WalletFileCorrupted)
135 }
136 }
137 }
138}
139
140pub fn check_version(bytes: Option<&[u8]>) -> Result<DatFileVersion> {
144 match bytes {
145 Some(bytes) => {
146 let header_bytes: [u8; 4] = bytes[0..4]
147 .try_into()
148 .map_err(|_| Error::WalletFileCorrupted)?;
149
150 let magic = u32::from_le_bytes(header_bytes) & 0x00ffffff;
151
152 if magic == OLD_MAGIC {
153 let (major, minor) = (bytes[3], bytes[4]);
155
156 Ok(DatFileVersion::OldWalletCli((major, minor, 0, 0, false)))
157 } else {
158 let header_bytes = bytes[0..8]
159 .try_into()
160 .map_err(|_| Error::WalletFileCorrupted)?;
161
162 let number = u64::from_be_bytes(header_bytes);
163
164 let magic_num = (number & 0xFFFFFF00000000) >> 32;
165
166 if (magic_num as u32) != MAGIC {
167 return Ok(DatFileVersion::Legacy);
168 }
169
170 let file_type = (number & 0x000000FFFF0000) >> 16;
171 let reserved = number & 0x0000000000FFFF;
172
173 if file_type != FILE_TYPE as u64 {
174 return Err(Error::WalletFileCorrupted);
175 };
176
177 if reserved != RESERVED as u64 {
178 return Err(Error::WalletFileCorrupted);
179 };
180
181 let version_bytes = bytes[8..12]
182 .try_into()
183 .map_err(|_| Error::WalletFileCorrupted)?;
184
185 let version = u32::from_be_bytes(version_bytes);
186
187 let major = (version & 0xff000000) >> 24;
188 let minor = (version & 0x00ff0000) >> 16;
189 let patch = (version & 0x0000ff00) >> 8;
190 let pre = (version & 0x000000f0) >> 4;
191 let higher = version & 0x0000000f;
192
193 let pre_higher = matches!(higher, 1);
194
195 Ok(DatFileVersion::RuskBinaryFileFormat((
196 major as u8,
197 minor as u8,
198 patch as u8,
199 pre as u8,
200 pre_higher,
201 )))
202 }
203 }
204 None => Err(Error::WalletFileCorrupted),
205 }
206}
207
208pub fn encrypt_seed(seed: &[u8; 64], pwd: &str, last_pos: u64) -> Result<Vec<u8>> {
209 let mut header = Vec::with_capacity(12);
210 header.extend_from_slice(&MAGIC.to_be_bytes());
211 header.extend_from_slice(&FILE_TYPE.to_be_bytes());
213 header.extend_from_slice(&RESERVED.to_be_bytes());
215 header.extend_from_slice(&version_bytes(LATEST_VERSION));
217
218 let mut payload = Vec::from(seed);
219
220 payload.extend(last_pos.to_le_bytes());
221
222 let pwd = blake3::hash(pwd.as_bytes());
223 let pwd = pwd.as_bytes();
224
225 payload = encrypt(&payload, pwd)?;
227
228 let mut content = Vec::with_capacity(header.len() + payload.len());
229
230 content.extend_from_slice(&header);
231 content.extend_from_slice(&payload);
232
233 Ok(content)
234}
235
236pub(crate) fn version_bytes(version: Version) -> [u8; 4] {
237 u32::from_be_bytes([version.0, version.1, version.2, version.3]).to_be_bytes()
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use alloc::vec::Vec;
244
245 #[test]
246 fn distiction_between_versions() {
247 let old_wallet_file = Vec::from([0x15, 0x0c, 0x1d, 0x02, 0x00]);
249 let legacy_file = Vec::from([
251 0xab, 0x38, 0x81, 0x3b, 0xfc, 0x79, 0x11, 0xf9, 0x86, 0xd6, 0xd0,
252 ]);
253 let new_file = Vec::from([
255 0x00, 0x72, 0x73, 0x6b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
256 ]);
257
258 assert_eq!(
259 check_version(Some(&old_wallet_file)).unwrap(),
260 DatFileVersion::OldWalletCli((2, 0, 0, 0, false))
261 );
262
263 assert_eq!(
264 check_version(Some(&legacy_file)).unwrap(),
265 DatFileVersion::Legacy
266 );
267
268 assert_eq!(
269 check_version(Some(&new_file)).unwrap(),
270 DatFileVersion::RuskBinaryFileFormat((0, 0, 1, 0, false))
271 );
272 }
273
274 #[test]
275 fn generate_latest_version() {
276 let seed: [u8; 64] = [0; 64];
277 let encryped = encrypt_seed(&seed, "password", 304).unwrap();
278
279 let version = check_version(Some(&encryped)).unwrap();
280
281 assert_eq!(
282 version,
283 DatFileVersion::RuskBinaryFileFormat(LATEST_VERSION)
284 );
285
286 let (returned_seed, last_pos) =
287 get_seed_and_address(version, encryped, "password").unwrap();
288
289 assert_eq!(seed, returned_seed.as_bytes());
290
291 assert_eq!(last_pos, 304);
292 }
293}