diff --git a/cw_core/lib/encryption_file_utils.dart b/cw_core/lib/encryption_file_utils.dart index 72a601bc3c..54729b735f 100644 --- a/cw_core/lib/encryption_file_utils.dart +++ b/cw_core/lib/encryption_file_utils.dart @@ -1,41 +1,34 @@ -import 'dart:io'; -import 'dart:typed_data'; import 'package:cw_core/utils/file.dart' as file; -import 'package:cake_backup/backup.dart' as cwb; -EncryptionFileUtils encryptionFileUtilsFor(bool direct) => - direct ? XChaCha20EncryptionFileUtils() : Salsa20EncryhptionFileUtils(); +EncryptionFileUtils encryptionFileUtilsFor(bool isDirect) => + XChaCha20MigratingEncryptionFileUtils(isDirect: isDirect); abstract class EncryptionFileUtils { Future write({required String path, required String password, required String data}); Future read({required String path, required String password}); } -class Salsa20EncryhptionFileUtils extends EncryptionFileUtils { - // Requires legacy complex key + iv as password - @override - Future write( - {required String path, required String password, required String data}) async => - await file.write(path: path, password: password, data: data); +class XChaCha20MigratingEncryptionFileUtils extends EncryptionFileUtils { + XChaCha20MigratingEncryptionFileUtils({required this.isDirect}); - // Requires legacy complex key + iv as password - @override - Future read({required String path, required String password}) async => - await file.read(path: path, password: password); -} + final bool isDirect; -class XChaCha20EncryptionFileUtils extends EncryptionFileUtils { @override - Future write({required String path, required String password, required String data}) async { - final encrypted = await cwb.encrypt(password, Uint8List.fromList(data.codeUnits)); - await File(path).writeAsBytes(encrypted); + Future write({required String path, required String password, required String data}) { + return file.write( + path: path, + password: password, + data: data, + highEntropyPassphrase: !isDirect, + ); } @override - Future read({required String path, required String password}) async { - final file = File(path); - final encrypted = await file.readAsBytes(); - final bytes = await cwb.decrypt(password, encrypted); - return String.fromCharCodes(bytes); + Future read({required String path, required String password}) { + return file.read( + path: path, + password: password, + highEntropyPassphrase: !isDirect, + ); } } diff --git a/cw_core/lib/key.dart b/cw_core/lib/key.dart index 383cbfffe6..99570bc7ff 100644 --- a/cw_core/lib/key.dart +++ b/cw_core/lib/key.dart @@ -1,35 +1,8 @@ import 'package:encrypt/encrypt.dart' as encrypt; -const ivEncodedStringLength = 12; - String generateKey() { final key = encrypt.Key.fromSecureRandom(512); final iv = encrypt.IV.fromSecureRandom(8); return key.base64 + iv.base64; } - -List extractKeys(String key) { - final _key = key.substring(0, key.length - ivEncodedStringLength); - final iv = key.substring(key.length - ivEncodedStringLength); - - return [_key, iv]; -} - -Future encode( - {required encrypt.Key key, required encrypt.IV iv, required String data}) async { - final encrypter = encrypt.Encrypter(encrypt.Salsa20(key)); - final encrypted = encrypter.encrypt(data, iv: iv); - - return encrypted.base64; -} - -Future decode({required String password, required String data}) async { - final keys = extractKeys(password); - final key = encrypt.Key.fromBase64(keys.first); - final iv = encrypt.IV.fromBase64(keys.last); - final encrypter = encrypt.Encrypter(encrypt.Salsa20(key)); - final encrypted = encrypter.decrypt64(data, iv: iv); - - return encrypted; -} diff --git a/cw_core/lib/utils/file.dart b/cw_core/lib/utils/file.dart index 72fc948598..3ad3165d6b 100644 --- a/cw_core/lib/utils/file.dart +++ b/cw_core/lib/utils/file.dart @@ -1,21 +1,84 @@ +import 'dart:convert'; import 'dart:io'; -import 'package:cw_core/key.dart'; +import 'dart:typed_data'; + +import 'package:cake_backup/backup.dart' as cwb; import 'package:encrypt/encrypt.dart' as encrypt; -Future write({required String path, required String password, required String data}) async => - writeData(path: path, password: password, data: data); +const _ivEncodedStringLength = 12; -Future writeData( - {required String path, required String password, required String data}) async { - final keys = extractKeys(password); - final key = encrypt.Key.fromBase64(keys.first); - final iv = encrypt.IV.fromBase64(keys.last); - final encrypted = await encode(key: key, iv: iv, data: data); - final f = File(path); - f.writeAsStringSync(encrypted); +Future write({ + required String path, + required String password, + required String data, + bool? highEntropyPassphrase, +}) async { + await _writeXChaCha20( + path: path, + password: password, + data: data, + highEntropyPassphrase: highEntropyPassphrase ?? !Platform.isLinux, + ); } -Future read({required String path, required String password}) async { +Future read({ + required String path, + required String password, + bool? highEntropyPassphrase, +}) async { + final useHighEntropy = highEntropyPassphrase ?? !Platform.isLinux; + try { + return await _readXChaCha20(path: path, password: password); + } catch (e) { + final encrypted = await File(path).readAsBytes(); + if (encrypted.isNotEmpty && + (encrypted[0] == cwb.lowEntropyVersion || encrypted[0] == cwb.highEntropyVersion)) { + rethrow; + } + + final String data; + try { + data = await _readLegacy(path: path, password: password); + } catch (_) { + throw Exception('Failed to decrypt legacy file: invalid password or corrupted data'); + } + if (data.isEmpty) { + throw Exception('Failed to read data'); + } + + // Salsa20 is unauthenticated, so a wrong password decrypts to garbage instead of + // failing. Every payload written here is a JSON object with at least one key, so a + // correctly decrypted file always starts with '{"' and anything else is a bad + // password. + // There's 1 in 2^16 chance that this will be a false positive, but check later + // prevents any damage to the file caused by re-encryption. + if (!data.startsWith('{"')) { + throw Exception('Failed to decrypt legacy file: invalid password or corrupted data'); + } + + if (_isJson(data)) { + await _writeXChaCha20( + path: path, + password: password, + data: data, + highEntropyPassphrase: useHighEntropy, + ); + } + + return data; + } +} + +bool _isJson(String data) { + try { + json.decode(data); + return true; + } catch (_) { + return false; + } +} + +Future _readLegacy({required String path, required String password}) async { final file = File(path); if (!file.existsSync()) { @@ -24,5 +87,57 @@ Future read({required String path, required String password}) async { final encrypted = file.readAsStringSync(); - return decode(password: password, data: encrypted); + return _decode(password: password, data: encrypted); +} + +Future _writeXChaCha20({ + required String path, + required String password, + required String data, + required bool highEntropyPassphrase, +}) async { + final encrypted = await cwb.encrypt( + password, + Uint8List.fromList(utf8.encode(data)), + highEntropyPassphrase: highEntropyPassphrase, + ); + + final tmpFile = File('$path.tmp'); + tmpFile.writeAsBytesSync(encrypted, flush: true); + tmpFile.renameSync(path); +} + +Future _readXChaCha20({ + required String path, + required String password, +}) async { + final encrypted = await File(path).readAsBytes(); + final bytes = await cwb.decrypt(password, encrypted); + return _decodeUtf8(bytes); +} + +// Linux wallets written by the old XChaCha20EncryptionFileUtils stored one byte per +// UTF-16 code unit, so bytes above 0x7F in those files are Latin-1 rather than UTF-8. +String _decodeUtf8(Uint8List bytes) { + try { + return utf8.decode(bytes); + } on FormatException { + return String.fromCharCodes(bytes); + } +} + +List _extractKeys(String key) { + final k = key.substring(0, key.length - _ivEncodedStringLength); + final iv = key.substring(key.length - _ivEncodedStringLength); + + return [k, iv]; +} + +Future _decode({required String password, required String data}) async { + final keys = _extractKeys(password); + final key = encrypt.Key.fromBase64(keys.first); + final iv = encrypt.IV.fromBase64(keys.last); + final encrypter = encrypt.Encrypter(encrypt.Salsa20(key)); + + return encrypter.decrypt64(data, iv: iv); } diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart index 72ea98a977..d69300d172 100644 --- a/cw_core/lib/wallet_service.dart +++ b/cw_core/lib/wallet_service.dart @@ -1,17 +1,18 @@ import "dart:convert"; import "dart:io"; -import "package:cw_core/imported_nft.dart"; -import "package:cw_core/pathForWallet.dart"; -import "package:cw_core/spl_token.dart"; -import "package:cw_core/tron_token.dart"; -import "package:cw_core/utils/file.dart"; -import "package:cw_core/utils/print_verbose.dart"; -import "package:cw_core/wallet_base.dart"; -import "package:cw_core/wallet_credentials.dart"; -import "package:cw_core/wallet_info.dart"; -import "package:cw_core/wallet_type.dart"; -import "package:path/path.dart" as p; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/imported_nft.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/spl_token.dart'; +import 'package:cw_core/tron_token.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:path/path.dart' as p; abstract class WalletService { @@ -95,8 +96,15 @@ abstract class WalletService getSeeds(String name, String password, WalletType type) async { try { + final encryption = encryptionFileUtilsFor(Platform.isLinux); + + if (await WalletKeysFile.hasKeysFile(name, type)) { + final keysData = await WalletKeysFile.readKeysFile(name, type, password, encryption); + return keysData.mnemonic ?? keysData.altMnemonic ?? keysData.privateKey ?? ''; + } + final path = await pathForWallet(name: name, type: type); - final jsonSource = await read(path: path, password: password); + final jsonSource = await encryption.read(path: path, password: password); try { final data = json.decode(jsonSource) as Map; return data["mnemonic"] as String? ?? ""; diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 38a4e9a411..b7843f4b10 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -22,8 +22,7 @@ dependencies: cake_backup: git: url: https://github.com/cake-tech/cake_backup.git - ref: main - version: 1.0.0 + ref: b5d86a2a21e1c186cd0baa4df1f3a4f3f9413056 socks5_proxy: git: url: https://github.com/LacticWhale/socks_dart diff --git a/cw_core/test/encryption_file_utils_test.dart b/cw_core/test/encryption_file_utils_test.dart new file mode 100644 index 0000000000..bfd840f014 --- /dev/null +++ b/cw_core/test/encryption_file_utils_test.dart @@ -0,0 +1,380 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cake_backup/backup.dart' as cwb; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/key.dart'; +import 'package:cw_core/utils/file.dart' as encrypted_file; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:encrypt/encrypt.dart' as encrypt; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late Directory tmpDir; + + setUp(() { + tmpDir = Directory.systemTemp.createTempSync('cw_enc_'); + }); + + tearDown(() { + if (tmpDir.existsSync()) { + tmpDir.deleteSync(recursive: true); + } + }); + + String path([String name = 'wallet.json']) => '${tmpDir.path}/$name'; + + const walletJson = '{"mnemonic":"abandon ability able","privateKey":null}'; + + group('XChaCha20 round-trip', () { + test('writes and reads JSON with a generated high-entropy password', () async { + final password = generateKey(); + await encrypted_file.write( + path: path(), + password: password, + data: walletJson, + highEntropyPassphrase: true, + ); + + expect(await encrypted_file.read(path: path(), password: password), walletJson); + expect(File('${path()}.tmp').existsSync(), isFalse); + }); + + test('uses high-entropy (v3) vs low-entropy (v2) version bytes', () async { + final password = generateKey(); + + await encrypted_file.write( + path: path('high'), + password: password, + data: walletJson, + highEntropyPassphrase: true, + ); + expect(File(path('high')).readAsBytesSync().first, cwb.highEntropyVersion); + + await encrypted_file.write( + path: path('low'), + password: password, + data: walletJson, + highEntropyPassphrase: false, + ); + expect(File(path('low')).readAsBytesSync().first, cwb.lowEntropyVersion); + + expect(await encrypted_file.read(path: path('high'), password: password), walletJson); + expect(await encrypted_file.read(path: path('low'), password: password), walletJson); + }); + + test('read ignores highEntropyPassphrase because version is in the file', () async { + final password = generateKey(); + await encrypted_file.write( + path: path(), + password: password, + data: walletJson, + highEntropyPassphrase: true, + ); + + expect( + await encrypted_file.read( + path: path(), + password: password, + highEntropyPassphrase: false, + ), + walletJson, + ); + }); + + test('round-trips UTF-8 JSON with non-ASCII characters', () async { + final password = generateKey(); + const data = '{"name":"Café","mnemonic":"abandon"}'; + await encrypted_file.write( + path: path(), + password: password, + data: data, + highEntropyPassphrase: true, + ); + + expect(await encrypted_file.read(path: path(), password: password), data); + }); + + test('wrong password does not fall back to Salsa20 or rewrite the file', () async { + final password = generateKey(); + await encrypted_file.write( + path: path(), + password: password, + data: walletJson, + highEntropyPassphrase: true, + ); + final before = File(path()).readAsBytesSync(); + + await expectLater( + encrypted_file.read(path: path(), password: generateKey()), + throwsA(anything), + ); + expect(File(path()).readAsBytesSync(), before); + }); + + test('corrupt XChaCha20 blob is not treated as a legacy Salsa20 file', () async { + final password = generateKey(); + await encrypted_file.write( + path: path(), + password: password, + data: walletJson, + highEntropyPassphrase: true, + ); + final bytes = File(path()).readAsBytesSync(); + bytes[bytes.length - 1] = bytes[bytes.length - 1] ^ 0xFF; + File(path()).writeAsBytesSync(bytes); + + await expectLater( + encrypted_file.read(path: path(), password: password), + throwsA(anything), + ); + expect(File(path()).readAsBytesSync().first, cwb.highEntropyVersion); + }); + }); + + group('encryptionFileUtilsFor', () { + test('isDirect=false (generated password) writes high-entropy v3', () async { + final password = generateKey(); + final encryption = encryptionFileUtilsFor(false); + await encryption.write(path: path(), password: password, data: walletJson); + + expect(File(path()).readAsBytesSync().first, cwb.highEntropyVersion); + expect(await encryption.read(path: path(), password: password), walletJson); + }); + + test('isDirect=true (user password) writes low-entropy v2', () async { + const password = 'user-chosen-passphrase'; + final encryption = encryptionFileUtilsFor(true); + await encryption.write(path: path(), password: password, data: walletJson); + + expect(File(path()).readAsBytesSync().first, cwb.lowEntropyVersion); + expect(await encryption.read(path: path(), password: password), walletJson); + }); + + test('WalletKeysData JSON survives a write/read used by getSeeds', () async { + final password = generateKey(); + final keys = WalletKeysData( + mnemonic: 'abandon ability able about above absent absorb abstract', + privateKey: 'deadbeef', + ); + final encryption = encryptionFileUtilsFor(false); + await encryption.write(path: path(), password: password, data: keys.toJSON()); + + final decoded = json.decode(await encryption.read(path: path(), password: password)) + as Map; + final restored = WalletKeysData.fromJSON(decoded); + expect(restored.mnemonic, keys.mnemonic); + expect(restored.privateKey, keys.privateKey); + }); + }); + + group('legacy Salsa20 migration', () { + test('reads a Salsa20 wallet file and re-encrypts it as XChaCha20', () async { + final password = generateKey(); + File(path()).writeAsStringSync(_salsa20Encrypt(password, walletJson)); + expect(File(path()).readAsBytesSync().first, isNot(cwb.lowEntropyVersion)); + expect(File(path()).readAsBytesSync().first, isNot(cwb.highEntropyVersion)); + + final result = await encrypted_file.read( + path: path(), + password: password, + highEntropyPassphrase: true, + ); + + expect(result, walletJson); + expect(File(path()).readAsBytesSync().first, cwb.highEntropyVersion); + expect(await encrypted_file.read(path: path(), password: password), walletJson); + }); + + test('migrated file can be opened with EncryptionFileUtils', () async { + final password = generateKey(); + File(path()).writeAsStringSync(_salsa20Encrypt(password, walletJson)); + + final encryption = encryptionFileUtilsFor(false); + expect(await encryption.read(path: path(), password: password), walletJson); + expect(await encryption.read(path: path(), password: password), walletJson); + }); + + test('wrong password does not rewrite the Salsa20 file', () async { + final password = generateKey(); + final salsa = _salsa20Encrypt(password, walletJson); + File(path()).writeAsStringSync(salsa); + + await expectLater( + encrypted_file.read(path: path(), password: generateKey()), + throwsLegacyDecryptError, + ); + expect(File(path()).readAsStringSync(), salsa); + }); + + test('short or malformed password does not leak _readLegacy errors', () async { + final password = generateKey(); + final salsa = _salsa20Encrypt(password, walletJson); + File(path()).writeAsStringSync(salsa); + + await expectLater( + encrypted_file.read(path: path(), password: 'user-pass'), + throwsLegacyDecryptError, + ); + expect(File(path()).readAsStringSync(), salsa); + }); + + test('corrupt non-base64 file does not leak _readLegacy errors', () async { + File(path()).writeAsStringSync('not-valid-base64!!!'); + + await expectLater( + encrypted_file.read(path: path(), password: generateKey()), + throwsLegacyDecryptError, + ); + expect(File(path()).readAsStringSync(), 'not-valid-base64!!!'); + }); + + test('invalid UTF-8 legacy bytes do not leak _readLegacy errors', () async { + File(path()).writeAsBytesSync(const [0x00, 0xFF, 0xFE, 0x01]); + + await expectLater( + encrypted_file.read(path: path(), password: generateKey()), + throwsLegacyDecryptError, + ); + expect(File(path()).readAsBytesSync(), const [0x00, 0xFF, 0xFE, 0x01]); + }); + + test('rejects Salsa20 plaintext that does not start with {"', () async { + final password = generateKey(); + File(path()).writeAsStringSync(_salsa20Encrypt(password, 'not-json-payload')); + + await expectLater( + encrypted_file.read(path: path(), password: password), + throwsLegacyDecryptError, + ); + expect(File(path()).readAsBytesSync().first, isNot(cwb.highEntropyVersion)); + expect(File(path()).readAsBytesSync().first, isNot(cwb.lowEntropyVersion)); + }); + + test('does not re-encrypt Salsa20 data that starts with {" but is not JSON', () async { + final password = generateKey(); + const garbage = '{"not valid json'; + File(path()).writeAsStringSync(_salsa20Encrypt(password, garbage)); + + expect(await encrypted_file.read(path: path(), password: password), garbage); + expect(File(path()).readAsBytesSync().first, isNot(cwb.highEntropyVersion)); + expect(File(path()).readAsBytesSync().first, isNot(cwb.lowEntropyVersion)); + }); + + test( + 'wrong password that decrypts to {" does not rewrite the file', + () async { + final password = generateKey(); + final salsa = _salsa20Encrypt(password, walletJson); + File(path()).writeAsStringSync(salsa); + + final collision = _findWrongPasswordWithJsonObjectPrefix(salsa); + expect(collision, isNot(password)); + expect(_salsa20Decrypt(collision, salsa).startsWith('{"'), isTrue); + expect(() => json.decode(_salsa20Decrypt(collision, salsa)), throwsA(anything)); + + final result = await encrypted_file.read(path: path(), password: collision); + + expect(result.startsWith('{"'), isTrue); + expect(result, isNot(walletJson)); + expect(File(path()).readAsStringSync(), salsa); + }, + ); + }); + + group('legacy Linux XChaCha20 Latin-1 payloads', () { + test('decodes files stored as one byte per UTF-16 code unit', () async { + final password = generateKey(); + const data = '{"name":"Café","mnemonic":"abandon"}'; + // Old XChaCha20EncryptionFileUtils used String.codeUnits instead of utf8.encode. + final encrypted = await cwb.encrypt( + password, + Uint8List.fromList(data.codeUnits), + highEntropyPassphrase: true, + ); + File(path()).writeAsBytesSync(encrypted); + + expect(await encrypted_file.read(path: path(), password: password), data); + }); + }); + + group('generateKey', () { + test('produces unique passwords that still encrypt and decrypt', () async { + final a = generateKey(); + final b = generateKey(); + expect(a, isNot(b)); + expect(a.length, greaterThan(12)); + + await encrypted_file.write( + path: path(), + password: a, + data: walletJson, + highEntropyPassphrase: true, + ); + expect(await encrypted_file.read(path: path(), password: a), walletJson); + }); + }); +} + +final throwsLegacyDecryptError = throwsA( + isA().having( + (e) => e.toString(), + 'message', + contains('Failed to decrypt legacy file'), + ), +); + +({encrypt.Key key, encrypt.IV iv}) _salsa20Keys(String password) { + const ivEncodedStringLength = 12; + return ( + key: encrypt.Key.fromBase64(password.substring(0, password.length - ivEncodedStringLength)), + iv: encrypt.IV.fromBase64(password.substring(password.length - ivEncodedStringLength)), + ); +} + +encrypt.Encrypter _salsa20(String password) { + final keys = _salsa20Keys(password); + return encrypt.Encrypter(encrypt.Salsa20(keys.key)); +} + +String _salsa20Encrypt(String password, String data) { + final keys = _salsa20Keys(password); + return _salsa20(password).encrypt(data, iv: keys.iv).base64; +} + +String _salsa20Decrypt(String password, String data) { + final keys = _salsa20Keys(password); + return _salsa20(password).decrypt64(data, iv: keys.iv); +} + +List _salsa20DecryptBytes(String password, String data) { + final keys = _salsa20Keys(password); + return _salsa20(password).decryptBytes(encrypt.Encrypted.fromBase64(data), iv: keys.iv); +} + +String _passwordFromNonce(int nonce) { + final ivBytes = Uint8List(8); + ivBytes.buffer.asByteData().setUint64(0, nonce, Endian.little); + return encrypt.Key(Uint8List(32)).base64 + encrypt.IV(ivBytes).base64; +} + +/// Salsa20 has no MAC, so a wrong key decrypts to garbage. The first two bytes +/// are `{"` about once in 2^16 tries; keep going until that happens, then skip +/// the much rarer case where the garbage is also valid JSON (that would rewrite). +String _findWrongPasswordWithJsonObjectPrefix(String ciphertext) { + const maxAttempts = 1 << 20; + for (var nonce = 0; nonce < maxAttempts; nonce++) { + final candidate = _passwordFromNonce(nonce); + final bytes = _salsa20DecryptBytes(candidate, ciphertext); + if (bytes.length < 2 || bytes[0] != 0x7b || bytes[1] != 0x22) { + continue; + } + try { + json.decode(_salsa20Decrypt(candidate, ciphertext)); + } catch (_) { + return candidate; + } + } + fail('no Salsa20 {" prefix collision in $maxAttempts attempts'); +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index b19bf12397..763c60e52f 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -55,7 +55,7 @@ import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/utils/file.dart'; +import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; @@ -1486,7 +1486,8 @@ abstract class DashboardViewModelBase with Store { if (walletInfo.type == WalletType.bitcoin) { final password = await keyService.getWalletPassword(walletName: walletInfo.name); final path = await pathForWallet(name: walletInfo.name, type: walletInfo.type); - final jsonSource = await read(path: path, password: password); + final encryption = encryptionFileUtilsFor(SettingsStoreBase.walletPasswordDirectInput); + final jsonSource = await encryption.read(path: path, password: password); final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String?; diff --git a/pubspec.lock b/pubspec.lock index f37ff20e85..4cd50bf71c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -388,8 +388,8 @@ packages: dependency: "direct main" description: path: "." - ref: "3aba867dcab6737f6707782f5db15d71f303db38" - resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38" + ref: b5d86a2a21e1c186cd0baa4df1f3a4f3f9413056 + resolved-ref: b5d86a2a21e1c186cd0baa4df1f3a4f3f9413056 url: "https://github.com/cake-tech/cake_backup.git" source: git version: "1.0.0+1" diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 8aae31be0c..b44150d09a 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -85,7 +85,7 @@ dependencies: cake_backup: git: url: https://github.com/cake-tech/cake_backup.git - ref: 3aba867dcab6737f6707782f5db15d71f303db38 + ref: b5d86a2a21e1c186cd0baa4df1f3a4f3f9413056 flutter_plugin_android_lifecycle: 2.0.23 path_provider_android: ^2.2.1 shared_preferences_android: ^2.4.8 @@ -247,7 +247,7 @@ dev_dependencies: # cake_backup: # git: # url: https://github.com/cake-tech/cake_backup.git -# ref: 3aba867dcab6737f6707782f5db15d71f303db38 +# ref: b5d86a2a21e1c186cd0baa4df1f3a4f3f9413056 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml index 15910a4f20..2bf9d2ce11 100644 --- a/pubspec_overrides.yaml +++ b/pubspec_overrides.yaml @@ -174,7 +174,7 @@ dependency_overrides: cake_backup: git: url: https://github.com/cake-tech/cake_backup.git - ref: 3aba867dcab6737f6707782f5db15d71f303db38 + ref: b5d86a2a21e1c186cd0baa4df1f3a4f3f9413056 characters: git: url: https://github.com/dart-lang/core