Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cold-wallet-app/lib/services/vault_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class VaultContents {
if (!plaintext.startsWith('{')) {
return VaultContents(
mnemonic: plaintext,
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)],
);
}
final m = jsonDecode(plaintext) as Map<String, dynamic>;
Expand Down
16 changes: 6 additions & 10 deletions cold-wallet-app/test/add_account_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,27 @@ import 'package:quantus_cold_wallet/models/cold_account.dart';
void main() {
group('the slot an account derives from', () {
test('an indexed account sits at its index', () {
expect(ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy).templateIndex, 0);
expect(ColdAccount(label: 'Account 13', index: 12, scheme: DilithiumSchemeExtension.legacy).templateIndex, 12);
expect(ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87).templateIndex, 0);
expect(ColdAccount(label: 'Account 13', index: 12, scheme: DilithiumScheme.mlDsa87).templateIndex, 12);
});

test('a path following the wallet template counts as the index it names', () {
final typed = ColdAccount(
label: 'Typed',
path: ColdAccount(label: 'x', index: 9, scheme: DilithiumSchemeExtension.legacy).derivationPath,
scheme: DilithiumSchemeExtension.legacy,
path: ColdAccount(label: 'x', index: 9, scheme: DilithiumScheme.mlDsa87).derivationPath,
scheme: DilithiumScheme.mlDsa87,
);

expect(typed.templateIndex, 9);
});

test('a path from another wallet claims no slot', () {
expect(
ColdAccount(label: 'Elsewhere', path: "m/44'/1'/0'", scheme: DilithiumSchemeExtension.legacy).templateIndex,
ColdAccount(label: 'Elsewhere', path: "m/44'/1'/0'", scheme: DilithiumScheme.mlDsa87).templateIndex,
isNull,
);
expect(
ColdAccount(
label: 'Deeper',
path: "m/44'/189189'/7'/1'/2'",
scheme: DilithiumSchemeExtension.legacy,
).templateIndex,
ColdAccount(label: 'Deeper', path: "m/44'/189189'/7'/1'/2'", scheme: DilithiumScheme.mlDsa87).templateIndex,
isNull,
);
});
Expand Down
2 changes: 1 addition & 1 deletion cold-wallet-app/test/audit_regressions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Future<void> pumpRequest(WidgetTester tester, SigningRequest request) async {
ProviderScope(
overrides: [
addressesProvider.overrideWith(
(ref) => {wallet: ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)},
(ref) => {wallet: ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)},
),
checksumNameProvider.overrideWith((ref, address) async => 'check phrase'),
],
Expand Down
2 changes: 1 addition & 1 deletion cold-wallet-app/test/call_display_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Future<void> pumpSignScreen(WidgetTester tester, Uint8List payload) async {
ProviderScope(
overrides: [
addressesProvider.overrideWith(
(ref) => {signerAddress: ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)},
(ref) => {signerAddress: ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)},
),
checksumNameProvider.overrideWith((ref, address) async => 'check phrase'),
],
Expand Down
6 changes: 3 additions & 3 deletions cold-wallet-app/test/change_password_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class _FaultInjectingStorage extends TestFlutterSecureStoragePlatform {
}
}

final _accounts = [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)];
final _accounts = [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)];

void main() {
TestWidgetsFlutterBinding.ensureInitialized();
Expand Down Expand Up @@ -101,7 +101,7 @@ void main() {
await vault.createVault(
mnemonic: _mnemonic,
password: 'beta',
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)],
);

final fresh = ProviderContainer();
Expand All @@ -128,7 +128,7 @@ void main() {
await vault.createVault(
mnemonic: _mnemonic,
password: 'beta',
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)],
);
expect(await vault.isBiometricEnabled(), isTrue, reason: 'a bare key carries no pairing to check at startup');
await expectLater(vault.unlockWithBiometricKey(), throwsA(isA<SecretBoxAuthenticationError>()));
Expand Down
2 changes: 1 addition & 1 deletion cold-wallet-app/test/every_call_renders_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Future<void> pumpAt(WidgetTester tester, Uint8List payload, Size size, double sc
ProviderScope(
overrides: [
addressesProvider.overrideWith(
(ref) => {signerAddress: ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)},
(ref) => {signerAddress: ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)},
),
checksumNameProvider.overrideWith((ref, address) async => 'check phrase'),
],
Expand Down
33 changes: 15 additions & 18 deletions cold-wallet-app/test/multi_account_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ void main() {
mnemonic: mnemonic,
password: 'alpha',
enableBiometric: false,
accounts: [ColdAccount(label: 'One', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'One', index: 0, scheme: DilithiumScheme.mlDsa87)],
);

expect(
await controller.changePassword(currentPassword: 'alpha', newPassword: 'beta'),
PasswordChangeResult.changed,
);
await controller.addAccount(ColdAccount(label: 'Two', index: 1, scheme: DilithiumSchemeExtension.legacy));
await controller.addAccount(ColdAccount(label: 'Two', index: 1, scheme: DilithiumScheme.mlDsa87));

final reopened = await VaultService().unlockWithPassword('beta');
expect(reopened.mnemonic, mnemonic);
Expand All @@ -89,12 +89,12 @@ void main() {
mnemonic: mnemonic,
password: 'alpha',
enableBiometric: true,
accounts: [ColdAccount(label: 'One', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'One', index: 0, scheme: DilithiumScheme.mlDsa87)],
);
biometric.lock();

expect(await biometric.unlockWithBiometric(), isTrue);
await biometric.addAccount(ColdAccount(label: 'Two', index: 1, scheme: DilithiumSchemeExtension.legacy));
await biometric.addAccount(ColdAccount(label: 'Two', index: 1, scheme: DilithiumScheme.mlDsa87));

expect(container.read(accountsProvider), hasLength(2));
expect((await VaultService().unlockWithPassword('alpha')).accounts, hasLength(2));
Expand All @@ -104,29 +104,26 @@ void main() {
group('ColdAccount', () {
test('an index fills the wallet template', () {
expect(
ColdAccount(label: 'a', index: 3, scheme: DilithiumSchemeExtension.legacy).derivationPath,
HdWalletService.pathForIndex(3, DilithiumSchemeExtension.legacy),
ColdAccount(label: 'a', index: 3, scheme: DilithiumScheme.mlDsa87).derivationPath,
HdWalletService.pathForIndex(3, DilithiumScheme.mlDsa87),
);
});

test('a path is taken verbatim', () {
expect(
ColdAccount(label: 'a', path: "m/44'/189189'/9'/0'/0'", scheme: DilithiumSchemeExtension.legacy).derivationPath,
ColdAccount(label: 'a', path: "m/44'/189189'/9'/0'/0'", scheme: DilithiumScheme.mlDsa87).derivationPath,
"m/44'/189189'/9'/0'/0'",
);
});

test('needs exactly one of index or path', () {
expect(() => ColdAccount(label: 'a', scheme: DilithiumSchemeExtension.legacy), throwsArgumentError);
expect(() => ColdAccount(label: 'a', scheme: DilithiumScheme.mlDsa87), throwsArgumentError);
expect(
() => ColdAccount(label: 'a', index: 0, path: "m/44'", scheme: DilithiumSchemeExtension.legacy),
throwsArgumentError,
);
expect(() => ColdAccount(label: 'a', index: -1, scheme: DilithiumSchemeExtension.legacy), throwsArgumentError);
expect(
() => ColdAccount(label: 'a', path: 'not a path', scheme: DilithiumSchemeExtension.legacy),
() => ColdAccount(label: 'a', index: 0, path: "m/44'", scheme: DilithiumScheme.mlDsa87),
throwsArgumentError,
);
expect(() => ColdAccount(label: 'a', index: -1, scheme: DilithiumScheme.mlDsa87), throwsArgumentError);
expect(() => ColdAccount(label: 'a', path: 'not a path', scheme: DilithiumScheme.mlDsa87), throwsArgumentError);
});
});

Expand All @@ -135,8 +132,8 @@ void main() {
final contents = VaultContents(
mnemonic: mnemonic,
accounts: [
ColdAccount(label: 'One', index: 0, scheme: DilithiumSchemeExtension.legacy),
ColdAccount(label: 'Two', path: "m/44'/189189'/7'/0'/0'", scheme: DilithiumSchemeExtension.legacy),
ColdAccount(label: 'One', index: 0, scheme: DilithiumScheme.mlDsa87),
ColdAccount(label: 'Two', path: "m/44'/189189'/7'/0'/0'", scheme: DilithiumScheme.mlDsa87),
],
);
final decoded = VaultContents.decode(contents.encode());
Expand All @@ -159,7 +156,7 @@ void main() {
await pumpFor(
tester,
signerAddress,
held: {signerAddress: ColdAccount(label: 'One', index: 0, scheme: DilithiumSchemeExtension.legacy)},
held: {signerAddress: ColdAccount(label: 'One', index: 0, scheme: DilithiumScheme.mlDsa87)},
);

expect(find.text('Sign'), findsOneWidget);
Expand All @@ -170,7 +167,7 @@ void main() {
await pumpFor(
tester,
otherAddress,
held: {signerAddress: ColdAccount(label: 'One', index: 0, scheme: DilithiumSchemeExtension.legacy)},
held: {signerAddress: ColdAccount(label: 'One', index: 0, scheme: DilithiumScheme.mlDsa87)},
);

expect(find.textContaining('does not exist in this cold wallet'), findsOneWidget);
Expand Down
2 changes: 1 addition & 1 deletion cold-wallet-app/test/set_password_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ void main() {
theme: AppTheme.darkTheme(context),
home: SetPasswordScreen(
mnemonic: 'test mnemonic',
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)],
),
),
),
Expand Down
4 changes: 2 additions & 2 deletions cold-wallet-app/test/show_secret_phrase_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void main() {
mnemonic: _mnemonic,
password: 'alpha',
enableBiometric: false,
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)],
),
);

Expand Down Expand Up @@ -94,7 +94,7 @@ void main() {
mnemonic: _mnemonic,
password: 'alpha',
enableBiometric: false,
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumSchemeExtension.legacy)],
accounts: [ColdAccount(label: 'Account 1', index: 0, scheme: DilithiumScheme.mlDsa87)],
),
);

Expand Down
16 changes: 16 additions & 0 deletions mobile-app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,22 @@
"@importWalletValidationError": {
"description": "Validation error when recovery phrase word count is invalid"
},
"importWalletScanFailedTitle": "Account scan failed",
"@importWalletScanFailedTitle": {
"description": "Title of the dialog shown when looking up the imported wallet's accounts fails"
},
"importWalletScanFailedBody": "We could not reach the network to look for your accounts. Try again now, or skip and the app will scan again the next time it starts.",
"@importWalletScanFailedBody": {
"description": "Body of the dialog shown when looking up the imported wallet's accounts fails"
},
"importWalletScanRetry": "Try again",
"@importWalletScanRetry": {
"description": "Button that runs the account scan again"
},
"importWalletScanSkip": "Skip",
"@importWalletScanSkip": {
"description": "Button that finishes the import without scanning for accounts"
},

"homeError": "Error: {error}",
"@homeError": {
Expand Down
4 changes: 4 additions & 0 deletions mobile-app/lib/l10n/app_id.arb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"importWalletHint": "Ketik atau tempel recovery phrase Anda. Pisahkan kata dengan spasi.",
"importWalletButton": "Impor",
"importWalletValidationError": "Recovery phrase harus 12 atau 24 kata",
"importWalletScanFailedTitle": "Pemindaian akun gagal",
"importWalletScanFailedBody": "Kami tidak dapat terhubung ke jaringan untuk mencari akun Anda. Coba lagi sekarang, atau lewati dan aplikasi akan memindai lagi saat dibuka berikutnya.",
"importWalletScanRetry": "Coba lagi",
"importWalletScanSkip": "Lewati",
"homeError": "Gagal: {error}",
"homeNoActiveAccount": "Tidak ada akun aktif",
"homeErrorLoadingBalance": "Gagal memuat saldo",
Expand Down
24 changes: 24 additions & 0 deletions mobile-app/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,30 @@ abstract class AppLocalizations {
/// **'Recovery phrase must be 12 or 24 words'**
String get importWalletValidationError;

/// Title of the dialog shown when looking up the imported wallet's accounts fails
///
/// In en, this message translates to:
/// **'Account scan failed'**
String get importWalletScanFailedTitle;

/// Body of the dialog shown when looking up the imported wallet's accounts fails
///
/// In en, this message translates to:
/// **'We could not reach the network to look for your accounts. Try again now, or skip and the app will scan again the next time it starts.'**
String get importWalletScanFailedBody;

/// Button that runs the account scan again
///
/// In en, this message translates to:
/// **'Try again'**
String get importWalletScanRetry;

/// Button that finishes the import without scanning for accounts
///
/// In en, this message translates to:
/// **'Skip'**
String get importWalletScanSkip;

/// Error message on the home screen
///
/// In en, this message translates to:
Expand Down
13 changes: 13 additions & 0 deletions mobile-app/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get importWalletValidationError => 'Recovery phrase must be 12 or 24 words';

@override
String get importWalletScanFailedTitle => 'Account scan failed';

@override
String get importWalletScanFailedBody =>
'We could not reach the network to look for your accounts. Try again now, or skip and the app will scan again the next time it starts.';

@override
String get importWalletScanRetry => 'Try again';

@override
String get importWalletScanSkip => 'Skip';

@override
String homeError(String error) {
return 'Error: $error';
Expand Down
13 changes: 13 additions & 0 deletions mobile-app/lib/l10n/app_localizations_id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get importWalletValidationError => 'Recovery phrase harus 12 atau 24 kata';

@override
String get importWalletScanFailedTitle => 'Pemindaian akun gagal';

@override
String get importWalletScanFailedBody =>
'Kami tidak dapat terhubung ke jaringan untuk mencari akun Anda. Coba lagi sekarang, atau lewati dan aplikasi akan memindai lagi saat dibuka berikutnya.';

@override
String get importWalletScanRetry => 'Coba lagi';

@override
String get importWalletScanSkip => 'Lewati';

@override
String homeError(String error) {
return 'Gagal: $error';
Expand Down
Loading
Loading