Skip to content

Commit 437616b

Browse files
authored
Merge pull request #1209 from SatoshiPortal/develop
5.4.4
2 parents f76c982 + 3ee2530 commit 437616b

352 files changed

Lines changed: 28990 additions & 3088 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/analyze.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ jobs:
1818

1919
steps:
2020
- name: Checkout Repository
21-
uses: actions/checkout@v4
22-
23-
- name: Setup Flutter
24-
uses: subosito/flutter-action@v2
25-
with:
26-
channel: stable
27-
flutter-version: 3.29.3
21+
uses: actions/checkout@v5
22+
23+
- name: Install FVM
24+
run: |
25+
curl -fsSL https://fvm.app/install.sh | bash
26+
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
2827
28+
- run: make fvm-check
2929
- run: make clean
3030
- run: make deps
3131
- run: make build-runner
3232
- run: make l10n
3333

3434
- name: "${{ matrix.analyze_mode.name }}"
35-
run: flutter analyze ${{ matrix.analyze_mode.mode }}
35+
run: fvm flutter analyze ${{ matrix.analyze_mode.mode }}
3636
continue-on-error: ${{ matrix.analyze_mode.allow_failure }}

.github/workflows/build.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,23 @@ env:
1212
jobs:
1313
ios:
1414
runs-on: macos-15
15-
1615
steps:
1716
- uses: maxim-lobanov/setup-xcode@v1
1817
with:
19-
xcode-version: latest-stable
18+
xcode-version: 16.4
2019

21-
- uses: actions/checkout@v4
20+
- uses: actions/checkout@v5
2221

2322
- uses: dtolnay/rust-toolchain@stable
2423

2524
- run: rustup target add aarch64-apple-ios
2625

27-
- uses: subosito/flutter-action@v2
28-
with:
29-
channel: stable
30-
architecture: x64
31-
flutter-version: 3.29.3
26+
- name: Install FVM
27+
run: |
28+
curl -fsSL https://fvm.app/install.sh | bash
29+
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
3230
31+
- run: make fvm-check
3332
- run: make clean
3433
- run: make deps
3534
- run: make build-runner
@@ -38,7 +37,7 @@ jobs:
3837
- run: cp .env.template .env
3938

4039
- name: Build no-codesign release
41-
run: flutter build ios --release --no-codesign
40+
run: fvm flutter build ios --release --no-codesign
4241

4342
- name: Upload artifact
4443
uses: actions/upload-artifact@v4

.github/workflows/test.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Checkout Repository
14-
uses: actions/checkout@v4
14+
uses: actions/checkout@v5
1515

16-
- name: Setup Flutter
17-
uses: subosito/flutter-action@v2
18-
with:
19-
channel: stable
20-
flutter-version: 3.29.3
16+
- name: Install FVM
17+
run: |
18+
curl -fsSL https://fvm.app/install.sh | bash
19+
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
2120
21+
- run: make fvm-check
2222
- run: make clean
2323
- run: make deps
2424
- run: make build-runner

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def flutterVersionName = localProperties.getProperty('flutter.versionName') ?: '
2525
android {
2626
compileSdk = 35
2727

28-
ndkVersion flutter.ndkVersion
28+
ndkVersion = "27.0.12077973"
2929
compileOptions {
3030
sourceCompatibility JavaVersion.VERSION_21
3131
targetCompatibility JavaVersion.VERSION_21
@@ -77,4 +77,4 @@ android {
7777

7878
flutter {
7979
source '../..'
80-
}
80+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:bb_mobile/core/bip85/data/bip85_datasource.dart';
4+
import 'package:bb_mobile/core/bip85/data/bip85_repository.dart';
5+
import 'package:bb_mobile/core/bip85/domain/derive_next_bip85_mnemonic_from_default_wallet_usecase.dart';
6+
import 'package:bb_mobile/core/seed/data/repository/seed_repository.dart';
7+
import 'package:bb_mobile/core/storage/sqlite_database.dart';
8+
import 'package:bb_mobile/core/storage/tables/bip85_derivations_table.dart';
9+
import 'package:bb_mobile/core/utils/bip32_derivation.dart';
10+
import 'package:bb_mobile/core/wallet/data/repositories/wallet_repository.dart';
11+
import 'package:bb_mobile/core/wallet/domain/entities/wallet.dart';
12+
import 'package:bb_mobile/core/wallet/domain/usecases/create_default_wallets_usecase.dart';
13+
import 'package:bb_mobile/locator.dart';
14+
import 'package:bb_mobile/main.dart';
15+
import 'package:bip39_mnemonic/bip39_mnemonic.dart' as bip39;
16+
import 'package:bip39_mnemonic/bip39_mnemonic.dart';
17+
import 'package:bip85/bip85.dart' as bip85;
18+
import 'package:flutter_test/flutter_test.dart';
19+
20+
Future<void> main({bool isInitialized = false}) async {
21+
TestWidgetsFlutterBinding.ensureInitialized();
22+
if (!isInitialized) await Bull.init();
23+
24+
final sqlite = locator<SqliteDatabase>();
25+
final seedRepository = locator<SeedRepository>();
26+
final walletRepository = locator<WalletRepository>();
27+
final bip85Datasource = locator<Bip85Datasource>();
28+
final bip85Repository = locator<Bip85Repository>();
29+
final createDefaultWalletsUsecase = locator<CreateDefaultWalletsUsecase>();
30+
31+
final mnemonic = Mnemonic.fromWords(
32+
words: [
33+
'zoo',
34+
'zoo',
35+
'zoo',
36+
'zoo',
37+
'zoo',
38+
'zoo',
39+
'zoo',
40+
'zoo',
41+
'zoo',
42+
'zoo',
43+
'zoo',
44+
'wrong',
45+
],
46+
);
47+
48+
final usecase = DeriveNextBip85MnemonicFromDefaultWalletUsecase(
49+
bip85Repository: bip85Repository,
50+
walletRepository: walletRepository,
51+
seedRepository: seedRepository,
52+
);
53+
54+
setUpAll(() async {
55+
// Clear all relevant tables before each test
56+
await sqlite.managers.bip85Derivations.delete();
57+
await sqlite.managers.walletMetadatas.delete();
58+
59+
await createDefaultWalletsUsecase.execute(mnemonicWords: mnemonic.words);
60+
});
61+
62+
setUp(() async {
63+
await sqlite.managers.bip85Derivations.delete();
64+
});
65+
66+
group(
67+
'DeriveNextBip85MnemonicFromDefaultWalletUsecase Integration Tests',
68+
() {
69+
test('one derivation with index 0 with empty table', () async {
70+
const length = bip39.MnemonicLength.words12;
71+
const index = 0;
72+
73+
// Execute the usecase
74+
final result = await usecase.execute(length: length, alias: 'test');
75+
76+
// Verify the derivation path
77+
expect(result.derivation, "39'/0'/12'/0'");
78+
expect(result.mnemonic, isA<bip39.Mnemonic>());
79+
expect(result.mnemonic.length, equals(length));
80+
81+
// Verify the derivation was stored in the database
82+
final storedDerivations = await bip85Datasource.fetchAll();
83+
expect(storedDerivations.length, 1);
84+
85+
// Ensure index 0, alias and application are correct
86+
final firstDerivation = storedDerivations.first;
87+
expect(firstDerivation.index, index);
88+
expect(firstDerivation.alias, 'test');
89+
expect(firstDerivation.application, Bip85ApplicationColumn.bip39);
90+
91+
// Get the xprv from the seed
92+
final xprv = Bip32Derivation.getXprvFromSeed(
93+
Uint8List.fromList(mnemonic.seed),
94+
Network.bitcoinMainnet,
95+
);
96+
97+
// Now generate the same derivation using BIP85 library directly
98+
final directBip85Mnemonic = bip85.Bip85Entropy.deriveMnemonic(
99+
xprvBase58: xprv,
100+
language: bip39.Language.english,
101+
length: length,
102+
index: index,
103+
);
104+
105+
// Verify both results are identical
106+
expect(result.mnemonic.sentence, directBip85Mnemonic.sentence);
107+
});
108+
109+
test('Two derivations with index bump and different lengths', () async {
110+
// Execute the usecase first time
111+
final a = await usecase.execute(
112+
length: bip39.MnemonicLength.words12,
113+
alias: 'First derivation',
114+
);
115+
116+
// Execute the usecase second time
117+
final b = await usecase.execute(
118+
length: bip39.MnemonicLength.words24,
119+
alias: 'Second derivation',
120+
);
121+
122+
// Get the xprv from the seed
123+
final xprv = Bip32Derivation.getXprvFromSeed(
124+
Uint8List.fromList(mnemonic.seed),
125+
Network.bitcoinMainnet,
126+
);
127+
128+
// Verify database has both derivations stored
129+
final storedDerivations = await bip85Datasource.fetchAll();
130+
expect(storedDerivations.length, equals(2));
131+
132+
final indices = storedDerivations.map((d) => d.index).toList()..sort();
133+
expect(indices, equals([0, 1]));
134+
135+
// Verify aliases are stored correctly
136+
final first = storedDerivations.firstWhere((d) => d.index == 0);
137+
expect(first.path, "39'/0'/12'/0'");
138+
expect(first.index, 0);
139+
expect(first.alias, 'First derivation');
140+
expect(first.application, equals(Bip85ApplicationColumn.bip39));
141+
expect(
142+
a.mnemonic.sentence,
143+
bip85.Bip85Entropy.deriveMnemonic(
144+
xprvBase58: xprv,
145+
language: bip39.Language.english,
146+
length: bip39.MnemonicLength.words12,
147+
index: 0,
148+
).sentence,
149+
);
150+
151+
final second = storedDerivations.firstWhere((d) => d.index == 1);
152+
expect(second.path, "39'/0'/24'/1'");
153+
expect(second.index, 1);
154+
expect(second.alias, 'Second derivation');
155+
expect(second.application, equals(Bip85ApplicationColumn.bip39));
156+
expect(
157+
b.mnemonic.sentence,
158+
bip85.Bip85Entropy.deriveMnemonic(
159+
xprvBase58: xprv,
160+
language: bip39.Language.english,
161+
length: bip39.MnemonicLength.words24,
162+
index: 1,
163+
).sentence,
164+
);
165+
});
166+
},
167+
);
168+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import 'package:bb_mobile/core/recoverbull/domain/entity/decrypted_vault.dart';
2+
import 'package:bb_mobile/core/recoverbull/domain/entity/encrypted_vault.dart';
3+
import 'package:bb_mobile/core/recoverbull/domain/usecases/decrypt_vault_usecase.dart';
4+
import 'package:bb_mobile/core/recoverbull/domain/usecases/restore_encrypted_vault_from_backup_key_usecase.dart';
5+
import 'package:bb_mobile/core/seed/data/models/seed_model.dart';
6+
import 'package:bb_mobile/core/seed/data/repository/seed_repository.dart';
7+
import 'package:bb_mobile/core/settings/domain/settings_entity.dart';
8+
import 'package:bb_mobile/core/tor/domain/usecases/initialize_tor_usecase.dart';
9+
import 'package:bb_mobile/core/wallet/data/repositories/wallet_repository.dart';
10+
import 'package:bb_mobile/features/key_server/domain/usecases/derive_backup_key_from_default_wallet_usecase.dart';
11+
12+
import 'package:bb_mobile/features/key_server/domain/usecases/restore_backup_key_from_password_usecase.dart';
13+
import 'package:bb_mobile/locator.dart';
14+
import 'package:bb_mobile/main.dart';
15+
import 'package:flutter_test/flutter_test.dart';
16+
17+
Future<void> main({bool isInitialized = false}) async {
18+
TestWidgetsFlutterBinding.ensureInitialized();
19+
if (!isInitialized) await Bull.init();
20+
21+
final initializeTorUsecase = locator<InitializeTorUsecase>();
22+
final restoreVaultKeyFromPasswordUsecase =
23+
locator<RestoreVaultKeyFromPasswordUsecase>();
24+
final restoreEncryptedVaultFromVaultKeyUsecase =
25+
locator<RestoreEncryptedVaultFromVaultKeyUsecase>();
26+
final deriveBackupKeyFromDefaultWalletUsecase =
27+
locator<DeriveBackupKeyFromDefaultWalletUsecase>();
28+
final decryptVaultUsecase = locator<DecryptVaultUsecase>();
29+
30+
final walletRepository = locator<WalletRepository>();
31+
final seedRepository = locator<SeedRepository>();
32+
33+
const backupZooMnemonicWithSevenZerosPassword =
34+
"""{"created_at":784044000000,"id":"09a6ed8f4de8fd73b73e2392ea78410b7b306d7090cd6f91ed91e7d1c1159799","ciphertext":"U2FiHun3tiRRzVIyJKWwPFmvnfzPJ/K/OzbASAoOIamOP4NRs8ADU7CR87NsxS5mp2dzbl3wgiquhCdQVABJXhHRpTQS7PlCwbbIg2Vj9o3PBoERCfeeD2KRv8uD+6HjNkm33zdHDK/dt1uAYUCcJtqP9ARhn+bUPlKBIW0XP/fIiH94LuU4+AXjN2WD8SBWX1VtS+CrORofA+eMLphLRh2ibzEGotvfrlp52/VjSd5sY3LGkr12lapLSfx4zILhgc2AqgUeFn4Nv8v8F6d3kZ372ikuie963MrncvTS4LxIVO723zX+Lp86bUcDXRtb6B4ZTVHhmRABGqYnviamf84dpcCbC2JhvPHBnOVGTMgf5KbIiBsCNFTKlRmaEnj2HSJLFeC6yBNop02jQ/XkgjFC+35Z7cvO2sKhB5Es0uo=","salt":"658d4287b027f95ae7e5b9f52a5439a4","path":"m/1608'/0'/586053381"}""";
35+
const password = '0000000';
36+
const vaultKey =
37+
'151a5a41f5eac5d49e67e0fad0bddd3beebe0f0e4b7739435997506cf12d9fce';
38+
const expectedMnemonicWords = [
39+
'zoo',
40+
'zoo',
41+
'zoo',
42+
'zoo',
43+
'zoo',
44+
'zoo',
45+
'zoo',
46+
'zoo',
47+
'zoo',
48+
'zoo',
49+
'zoo',
50+
'wrong',
51+
];
52+
53+
setUpAll(() async => await initializeTorUsecase.execute());
54+
55+
group('Recoverbull', () {
56+
test('Restore backup key from password', () async {
57+
final backupKey = await restoreVaultKeyFromPasswordUsecase.execute(
58+
vault: EncryptedVault(file: backupZooMnemonicWithSevenZerosPassword),
59+
password: password,
60+
);
61+
62+
expect(backupKey, isNotEmpty);
63+
});
64+
65+
test('Restore encrypted vault from backup key', () async {
66+
final backupKey = await restoreVaultKeyFromPasswordUsecase.execute(
67+
vault: EncryptedVault(file: backupZooMnemonicWithSevenZerosPassword),
68+
password: password,
69+
);
70+
expect(backupKey, isNotEmpty);
71+
72+
await restoreEncryptedVaultFromVaultKeyUsecase.execute(
73+
vault: EncryptedVault(file: backupZooMnemonicWithSevenZerosPassword),
74+
vaultKey: backupKey,
75+
);
76+
77+
final wallets = await walletRepository.getWallets(
78+
onlyDefaults: true,
79+
onlyBitcoin: true,
80+
environment: Environment.mainnet,
81+
);
82+
83+
expect(wallets.length, 1);
84+
final wallet = wallets.first;
85+
expect(wallet.masterFingerprint, isNotEmpty);
86+
final seed = await seedRepository.get(wallet.masterFingerprint);
87+
final seedModel = SeedModel.fromEntity(seed);
88+
expect(seedModel, isA<MnemonicSeedModel>());
89+
final mnemonicSeedModel = seedModel as MnemonicSeedModel;
90+
expect(mnemonicSeedModel.mnemonicWords, equals(expectedMnemonicWords));
91+
});
92+
93+
test('Derive backup key from default wallet', () async {
94+
final derivedKey = await deriveBackupKeyFromDefaultWalletUsecase.execute(
95+
vault: EncryptedVault(file: backupZooMnemonicWithSevenZerosPassword),
96+
);
97+
expect(derivedKey, vaultKey);
98+
});
99+
100+
test('Decrypt vault from backup key', () {
101+
final decryptedVault = decryptVaultUsecase.execute(
102+
vault: EncryptedVault(file: backupZooMnemonicWithSevenZerosPassword),
103+
vaultKey: vaultKey,
104+
);
105+
expect(decryptedVault, isA<DecryptedVault>());
106+
});
107+
});
108+
}

ios/Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ post_install do |installer|
5454
]
5555
end
5656
end
57-
end
57+
end

0 commit comments

Comments
 (0)