rusk_wallet/
dat.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//! Methods for parsing/checking the DAT wallet file
8
9use std::fs;
10use std::io::Read;
11
12use wallet_core::Seed;
13
14use crate::crypto::decrypt;
15use crate::{Error, WalletPath};
16
17/// Binary prefix for old Dusk wallet files
18pub const OLD_MAGIC: u32 = 0x1d0c15;
19/// Binary prefix for new binary file format
20pub const MAGIC: u32 = 0x72736b;
21/// The latest version of the rusk binary format for wallet dat file
22pub const LATEST_VERSION: Version = (0, 0, 1, 0, false);
23/// The type info of the dat file we'll save
24pub const FILE_TYPE: u16 = 0x0200;
25/// Reserved for futures use, 0 for now
26pub const RESERVED: u16 = 0x0000;
27/// (Major, Minor, Patch, Pre, Pre-Higher)
28type Version = (u8, u8, u8, u8, bool);
29
30/// Versions of the potential wallet DAT files we read
31#[derive(Copy, Clone, Debug, PartialEq)]
32pub enum DatFileVersion {
33    /// Legacy the oldest format
34    Legacy,
35    /// Preciding legacy, we have the old one
36    OldWalletCli(Version),
37    /// The newest one. All new saves are saved in this file format
38    RuskBinaryFileFormat(Version),
39}
40
41impl DatFileVersion {
42    /// Checks if the file version is older than the latest Rust Binary file
43    /// format
44    pub fn is_old(&self) -> bool {
45        matches!(self, Self::Legacy | Self::OldWalletCli(_))
46    }
47}
48
49/// Make sense of the payload and return it
50pub(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            // get our seed
64            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                    // extract seed
89                    let seed = buff
90                        .try_into()
91                        .map_err(|_| Error::WalletFileCorrupted)?;
92
93                    // extract addresses count
94                    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
124/// From the first 12 bytes of the file (header), we check version
125///
126/// https://github.com/dusk-network/rusk/wiki/Binary-File-Format/#header
127pub(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                // check for version information
140                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
194/// Read the first 12 bytes of the dat file and get the file version from
195/// there
196pub fn read_file_version(file: &WalletPath) -> Result<DatFileVersion, Error> {
197    let path = &file.wallet;
198
199    // make sure file exists
200    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        // with magic number
225        let old_wallet_file = vec![0x15, 0x0c, 0x1d, 0x02, 0x00];
226        // no magic number just nonsense bytes
227        let legacy_file = vec![
228            0xab, 0x38, 0x81, 0x3b, 0xfc, 0x79, 0x11, 0xf9, 0x86, 0xd6, 0xd0,
229        ];
230        // new header
231        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}