Skip to content

Commit dddeaf3

Browse files
authored
Merge pull request #9 from vault12/code-cleanup-2025
Code cleanup and minor improvements
2 parents 09de810 + 330e12d commit dddeaf3

4 files changed

Lines changed: 87 additions & 49 deletions

File tree

README.md

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,43 @@
77
<a href="https://opensource.org/licenses/MIT">
88
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License" />
99
</a>
10-
<img src="https://img.shields.io/david/vault12/recovery-utility" alt="Dependencies" />
1110
<a href="http://makeapullrequest.com">
1211
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome" />
1312
</a>
14-
<a href="https://twitter.com/_Vault12_">
15-
<img src="https://img.shields.io/twitter/follow/_Vault12_?label=Follow&style=social" alt="Follow" />
13+
<a href="https://x.com/_Vault12_">
14+
<img src="https://img.shields.io/twitter/follow/_Vault12_?label=Follow&style=social" alt="Follow us on X" />
1615
</a>
1716
</p>
1817

1918
# Overview
2019

21-
A command-line utility for recovering assets from the raw data exported from [Vault12 mobile app](https://vault12.com/download/).
20+
The Vault12 Recovery Utility is a command-line tool designed to recover assets from raw data exported from the [Vault12 Guard mobile app](https://vault12.com/download/).
2221

2322
## Installation
2423

25-
1. Install [node](https://nodejs.org/)
26-
2. Install the package globally:
27-
```
28-
$ npm i -g vault12-recovery
29-
```
24+
1. Install [Node.js](https://nodejs.org/).
25+
2. Install the package globally by running:
26+
```bash
27+
npm install -g vault12-recovery
28+
```
29+
3030
## Usage
3131

32-
1. Export the decryption key (`vault12.json`) from the Vault12 app: *Settings > Advanced > Export My Vault’s Decryption Key*.
33-
2. Collect the raw Vault data from several Guardian devices via *Settings > Advanced > Export Data for External Vault*. You'll need to collect the amount of files equal to the **Number of Confirmations** you selected when creating the Vault.
34-
3. Place all exported archives and `vault12.json` file in the same directory, e.g. `~/vault12-files`.
35-
4. In terminal, run
36-
```
37-
$ vault12-recovery ~/vault12-files
38-
```
39-
5. You should find all recovered assets from the Vault in the directory `~/vault12-files/output`.
32+
1. Export the decryption key (`vault12.json`) from the Vault12 app:
33+
- **Settings > Advanced > Export My Vault’s Decryption Key**.
34+
2. Collect the raw Vault data from multiple Guardian devices:
35+
- Use **Settings > Advanced > Export Data for External Vault**.
36+
- Ensure you collect the number of files equal to the **Number of Confirmations** you selected when creating the Vault.
37+
3. Place all exported archives and the `vault12.json` file in the same directory, e.g., `~/vault12-files`.
38+
4. Run the recovery utility in the terminal:
39+
```bash
40+
vault12-recovery ~/vault12-files
41+
```
42+
5. The recovered assets will be saved in the `~/vault12-files/output` directory.
4043

4144
## License
4245

43-
Vault12 Recovery Utility is released under the [MIT License](http://opensource.org/licenses/MIT).
46+
**Vault12 Recovery Utility** is released under the [MIT License](http://opensource.org/licenses/MIT).
4447

4548
## Legal Reminder
4649

index.ts

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,40 @@ const indexFile = 'index.json';
1717
console.log(chalk.whiteBright(`Vault12 Recovery Utility ${version}`));
1818
console.log('-------------------');
1919

20-
const dir = getDirectory();
21-
const vaultData = getVaultData();
22-
const zipArchives = getZipArchives();
23-
findAssetShardsInArchives();
24-
const workingOutputDir = createDir(path(outputDir));
25-
restoreAssets();
20+
let dir: string;
21+
let vaultData: ExportVaultMetadata;
22+
let zipArchives: string[];
23+
let workingOutputDir: string;
24+
25+
function main() {
26+
if (process.argv.length < 3) {
27+
console.log(chalk.red('Usage: vault12-recovery <directory>'));
28+
process.exit(1);
29+
}
30+
31+
try {
32+
dir = getDirectory();
33+
vaultData = getVaultData();
34+
zipArchives = getZipArchives();
35+
findAssetShardsInArchives();
36+
workingOutputDir = createDir(path(outputDir));
37+
restoreAssets();
38+
} catch (error) {
39+
console.error(chalk.red(error.message));
40+
process.exit(1);
41+
}
42+
}
43+
44+
main();
2645

2746
function getDirectory() {
2847
const dir = process.argv[2];
2948
if (!dir) {
3049
throw new Error('First parameter should be a directory');
3150
}
51+
if (!fs.existsSync(dir)) {
52+
throw new Error(`Directory ${dir} not found`);
53+
}
3254
if (!fs.lstatSync(dir).isDirectory()) {
3355
throw new Error(`${dir} expected to be a directory`);
3456
}
@@ -37,6 +59,9 @@ function getDirectory() {
3759

3860
function getVaultData(): ExportVaultMetadata {
3961
const keyFilePath = path(keyFile);
62+
if (!fs.existsSync(keyFilePath)) {
63+
throw new Error(`Key file ${keyFilePath} not found`);
64+
}
4065
if (!fs.lstatSync(keyFilePath).isFile()) {
4166
throw new Error(`Expected ${dir} to contain ${keyFilePath}`);
4267
}
@@ -56,27 +81,37 @@ function getZipArchives() {
5681
function restoreAssets() {
5782
const masterKey = Buffer.from(vaultData.masterKey, 'base64');
5883
const assetsCount = vaultData.assetsMetaData.length;
59-
84+
let hasErrors = false; // Track if any errors occur
6085
vaultData.assetsMetaData.forEach((asset, i) => {
6186
process.stdout.write(`${chalk.yellow(`${i+1}/${assetsCount}`)} Unlocking ${chalk.bold(asset.name)}... `);
6287
let recombinedFile: Buffer;
6388
try {
64-
recombinedFile = recombineAsset(asset)
89+
recombinedFile = recombineAsset(asset);
6590
} catch (error) {
66-
console.error(`Failed to recover ${asset.name}`, error);
91+
console.error(`Failed to recover ${asset.name}`, error.message);
92+
hasErrors = true;
6793
return;
6894
}
6995
let plainText: Uint8Array;
7096
try {
71-
plainText = decryptAsset(asset, recombinedFile, masterKey)
97+
plainText = decryptAsset(asset, recombinedFile, masterKey);
7298
} catch (error) {
73-
console.error(`Failed to decrypt ${asset.name}`, error);
99+
console.error(`Failed to decrypt ${asset.name}`, error.message);
100+
hasErrors = true;
74101
return;
75102
}
76-
fs.writeFileSync(path(outputDir, asset.name), plainText);
77-
process.stdout.write(chalk.green('✓') + '\n');
78-
})
79-
console.log(chalk.green(`Assets successfully unlocked and stored in ${workingOutputDir}`));
103+
try {
104+
fs.writeFileSync(path(outputDir, asset.name), plainText);
105+
process.stdout.write(chalk.green('✓') + '\n');
106+
} catch (error) {
107+
console.error(`Failed to write ${asset.name} to disk`, error.message);
108+
hasErrors = true;
109+
}
110+
});
111+
112+
if (!hasErrors) {
113+
console.log(chalk.green(`Assets successfully unlocked and stored in ${workingOutputDir}`));
114+
}
80115
}
81116

82117
function createDir(workingOutputDir: string) {
@@ -87,7 +122,7 @@ function createDir(workingOutputDir: string) {
87122
}
88123

89124
function findAssetShardsInArchives() {
90-
console.log(`Validating Vault...`);
125+
console.log(chalk.yellow(`Validating Vault...`));
91126
zipArchives.forEach(zipArchive => {
92127
const archiveDirName = pathLib.parse(zipArchive).name;
93128

@@ -137,14 +172,13 @@ function recombineAsset(asset: ExportAssetMetadata) {
137172
const shardPaths = asset.shards.filter(shard => !!shard.path).map(shard => shard.path);
138173

139174
if (shardPaths.length < vaultData.shardsRequiredToUnlock) {
140-
console.error(`Only ${shardPaths.length} shards found for asset ${asset.name} when ${vaultData.shardsRequiredToUnlock} needed`);
141-
return;
175+
throw new Error(`Only ${shardPaths.length} shards found for asset ${asset.name} when ${vaultData.shardsRequiredToUnlock} needed`);
142176
}
143177
/**
144178
* prepare for format required by shamir
145179
* 1st byte of file is index of shard the rest is data
146180
*/
147-
const buffers = shardPaths.map((path) => fs.readFileSync(path));
181+
const buffers = shardPaths.filter((path): path is string => !!path).map((path) => fs.readFileSync(path));
148182
const obj = {};
149183
buffers.forEach((v) => obj[v[0]] = v.slice(1));
150184
return join(obj);

package-lock.json

Lines changed: 11 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vault12-recovery",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"description": "A command-line utility for recovering assets from the raw data exported from Vault12 mobile app",
55
"main": "index.js",
66
"author": "Vault12",
@@ -32,7 +32,7 @@
3232
"devDependencies": {
3333
"@types/adm-zip": "0.4.33",
3434
"@types/node": "14.14.25",
35-
"typescript": "4.2.2"
35+
"typescript": "5.8.3"
3636
},
3737
"dependencies": {
3838
"adm-zip": "0.5.2",

0 commit comments

Comments
 (0)