Skip to content

Commit

Permalink
Save private key #18 - saving
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanofornari committed Jun 6, 2022
1 parent f791244 commit 26937bc
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"shared_preferences_ios","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_ios-2.1.0/","native_build":true,"dependencies":[]}],"android":[{"name":"shared_preferences_android","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_android-2.0.11/","native_build":true,"dependencies":[]}],"macos":[{"name":"shared_preferences_macos","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-2.0.3/","native_build":true,"dependencies":[]},{"name":"window_size","path":"/usr/lib/flutter/.pub-cache/git/flutter-desktop-embedding-89c350f787e1d7bff12b3517e5671146211ee70e/plugins/window_size/","native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.5/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_linux-2.1.0/","native_build":false,"dependencies":["path_provider_linux"]},{"name":"window_size","path":"/usr/lib/flutter/.pub-cache/git/flutter-desktop-embedding-89c350f787e1d7bff12b3517e5671146211ee70e/plugins/window_size/","native_build":true,"dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.0.5/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_windows-2.1.0/","native_build":false,"dependencies":["path_provider_windows"]},{"name":"window_size","path":"/usr/lib/flutter/.pub-cache/git/flutter-desktop-embedding-89c350f787e1d7bff12b3517e5671146211ee70e/plugins/window_size/","native_build":true,"dependencies":[]}],"web":[{"name":"shared_preferences_web","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-2.0.3/","dependencies":[]}]},"dependencyGraph":[{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_ios","shared_preferences_linux","shared_preferences_macos","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_ios","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"window_size","dependencies":[]}],"date_created":"2022-06-04 17:36:04.435418","version":"3.1.0-0.0.pre.1101"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"shared_preferences_ios","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_ios-2.1.0/","native_build":true,"dependencies":[]}],"android":[{"name":"shared_preferences_android","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_android-2.0.11/","native_build":true,"dependencies":[]}],"macos":[{"name":"shared_preferences_macos","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-2.0.3/","native_build":true,"dependencies":[]},{"name":"window_size","path":"/usr/lib/flutter/.pub-cache/git/flutter-desktop-embedding-89c350f787e1d7bff12b3517e5671146211ee70e/plugins/window_size/","native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.5/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_linux-2.1.0/","native_build":false,"dependencies":["path_provider_linux"]},{"name":"window_size","path":"/usr/lib/flutter/.pub-cache/git/flutter-desktop-embedding-89c350f787e1d7bff12b3517e5671146211ee70e/plugins/window_size/","native_build":true,"dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.0.5/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_windows-2.1.0/","native_build":false,"dependencies":["path_provider_windows"]},{"name":"window_size","path":"/usr/lib/flutter/.pub-cache/git/flutter-desktop-embedding-89c350f787e1d7bff12b3517e5671146211ee70e/plugins/window_size/","native_build":true,"dependencies":[]}],"web":[{"name":"shared_preferences_web","path":"/usr/lib/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-2.0.3/","dependencies":[]}]},"dependencyGraph":[{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_ios","shared_preferences_linux","shared_preferences_macos","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_ios","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"window_size","dependencies":[]}],"date_created":"2022-06-06 08:38:49.429855","version":"3.1.0-0.0.pre.1109"}
2 changes: 2 additions & 0 deletions lib/easy_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class EasyWallet {
final String address;

BigDecimal balance;
String privateKey = "";
String mnemonic = "";

EasyWallet(this.address) : balance = BigDecimal.parse("0.0");

Expand Down
19 changes: 12 additions & 7 deletions lib/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import 'package:easy_wallet/resources/constants.dart';
import 'easy_wallet.dart';

class _WalletPrefereces {
late String address;
late Map wallet;

_WalletPrefereces(EasyWallet w) {
address = w.address;
wallet = {
KEY_CFG_WALLET_ADDRESS: w.address,
KEY_CFG_WALLET_PRIVATE_KEY: w.privateKey,
KEY_CFG_WALLET_MNEMONIC: w.mnemonic
};
}

Map toJson() => {
"address": address
};
Map toJson() => wallet;
}

class Preferences {
Expand All @@ -35,7 +37,10 @@ class Preferences {
p.appkey = jsonMap[KEY_CFG_APPKEY] ?? "";
p.wallets = [];
for (Map w in jsonMap[KEY_CFG_WALLETS] ?? {}) {
p.wallets.add(EasyWallet(w[KEY_CFG_WALLET_ADDRESS]));
EasyWallet wallet = EasyWallet(w[KEY_CFG_WALLET_ADDRESS]);
wallet.privateKey = w[KEY_CFG_WALLET_PRIVATE_KEY] ?? "";
wallet.mnemonic = w[KEY_CFG_WALLET_MNEMONIC] ?? "";
p.wallets.add(wallet);
}

return p;
Expand All @@ -52,7 +57,7 @@ class Preferences {
for(EasyWallet w in wallets) {
list.add(_WalletPrefereces(w));
};
json["wallets"] = list;
json[KEY_CFG_WALLETS] = list;

return json;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/resources/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const String KEY_CFG_ENDPOINT = "endpoint";
const String KEY_CFG_APPKEY = "appkey";
const String KEY_CFG_WALLETS = "wallets";
const String KEY_CFG_WALLET_ADDRESS = "address";
const String KEY_CFG_WALLET_PRIVATE_KEY = "privateKey";
const String KEY_CFG_WALLET_MNEMONIC = "mnemonicPhrase";

const String LABEL_ADDRESS = "Insert the 20 hex bytes public address:";
const String LABEL_ADDRESS_HINT = "eg: 00000000219ab540356cBB839Cbe05303d7705Fa";
Expand Down
15 changes: 8 additions & 7 deletions lib/ui/edit_private_key_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ class EditPrivateKeyDialog extends StatefulWidget {
class _EditPrivateKeyDialogState extends State<EditPrivateKeyDialog> {
final EasyWallet wallet;
final WalletEditingController keyController = WalletEditingController();
final MnemonicEditingController passphraseController = MnemonicEditingController();
final MnemonicEditingController mnemonicController = MnemonicEditingController();

_EditPrivateKeyDialogState(this.wallet);

@override
void initState() {
super.initState();
keyController.clear();
passphraseController.clear();
mnemonicController.clear();
}

@override
void dispose() {
super.dispose();
keyController.dispose();
passphraseController.dispose();
mnemonicController.dispose();
}

@override
Expand All @@ -46,7 +46,7 @@ class _EditPrivateKeyDialogState extends State<EditPrivateKeyDialog> {
Text(LABEL_MNEMONIC_PHRASE),
TextField(
key: KEY_MNEMONIC_PHRASE,
controller: passphraseController,
controller: mnemonicController,
onChanged: (value) {
_setPrivateKeyIfValidPassphrase(value);
setState(() {});
Expand Down Expand Up @@ -89,6 +89,8 @@ class _EditPrivateKeyDialogState extends State<EditPrivateKeyDialog> {
TextButton(
child: const Text('OK'),
onPressed: !_validate() ? null : () {
wallet.privateKey = keyController.text;
wallet.mnemonic = mnemonicController.text;
Navigator.pop(context, "");
}
)
Expand All @@ -102,16 +104,15 @@ class _EditPrivateKeyDialogState extends State<EditPrivateKeyDialog> {
//
// If the key is invalid key2adress returns an empty address...
//
print("${wallet.address} == " + keyController.key2address());
return wallet.address == keyController.key2address();

}

bool _isPrivateKeyEnabled() {
return passphraseController.value.text.isEmpty;
return mnemonicController.value.text.isEmpty;
}

void _setPrivateKeyIfValidPassphrase(value) {
keyController.text = passphraseController.privateKey(wallet.address);
keyController.text = mnemonicController.privateKey(wallet.address);
}
}
10 changes: 7 additions & 3 deletions lib/ui/wallet_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,13 @@ class _EasyWalletState extends State<EasyWalletHomePage> {

for (var wallet in controller.value) {
cards.add(
WalletCard(wallet, () {
controller - wallet.address;
setState(() {});
WalletCard(wallet, (WalletAction action) {
if (action == WalletAction.delete) {
controller - wallet.address;
setState(() {});
} else if (action == WalletAction.update) {
controller.updateWallet(wallet);
}
})
);
}
Expand Down
10 changes: 7 additions & 3 deletions lib/ui/wallet_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import "package:crypto_font_icons/crypto_font_icons.dart";
import 'package:easy_wallet/easy_wallet.dart';
import 'package:easy_wallet/ui/edit_private_key_dialog.dart';

enum WalletAction { delete, update }


class WalletCard extends StatelessWidget {

final EasyWallet wallet;
final Function onDelete;
final Function onUpdate;

WalletCard(this.wallet, this.onDelete) : super (
WalletCard(this.wallet, this.onUpdate) : super (
key: Key(wallet.address),
) { }

Expand Down Expand Up @@ -72,7 +74,7 @@ class WalletCard extends StatelessWidget {
child: const Icon(Icons.delete),
),
onTap: () {
onDelete();
onUpdate(WalletAction.delete);
},
),
]
Expand Down Expand Up @@ -166,6 +168,8 @@ class WalletCard extends StatelessWidget {
return EditPrivateKeyDialog(wallet);
});

onUpdate(WalletAction.update);

return result;
}

Expand Down
18 changes: 14 additions & 4 deletions lib/ui/wallet_list_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,39 @@ class WalletListController extends ValueNotifier<List<EasyWallet>> {

WalletListController operator +(EasyWallet wallet) {
value.add(wallet);
walletManager.balance(wallet).whenComplete(() => notifyListeners());
walletManager.balance(wallet).whenComplete(() => notifyListeners()); // TODO: remove notifyListener

_savePreferences();

return this;
}

WalletListController operator -(String address) {
final int l = value.length;
final int len = value.length;
value.removeWhere((e) {
return (e.address == address);
});

if (value.length != l) {
if (value.length != len) {
_savePreferences();
}

return this;
}

void updateWallet(EasyWallet wallet) {
for (int i=0; i<value.length; ++i) {
if (value[i].address == wallet.address) {
value[i] = wallet;
break;
}
}
_savePreferences();
}

Future<void> retrieveBalance() async {
for (EasyWallet w in value) {
await walletManager.balance(w); notifyListeners();
await walletManager.balance(w); notifyListeners(); // TODO: remove notifyListener
}
}

Expand Down
36 changes: 31 additions & 5 deletions test/preferences_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ void main() {

p.endpoint = "endpoint";
p.appkey = "appkey";

p.wallets = [
EasyWallet("wallet1")
];
expect(json.encoder.convert(p), '{"endpoint":"endpoint","appkey":"appkey","wallets":[{"address":"wallet1"}]}');
p.wallets[0].privateKey = "privatekey1";
expect(json.encoder.convert(p), '{"endpoint":"endpoint","appkey":"appkey","wallets":[{"address":"wallet1","privateKey":"privatekey1","mnemonicPhrase":""}]}');

p.wallets.add(EasyWallet("wallet2"));
expect(json.encoder.convert(p), '{"endpoint":"endpoint","appkey":"appkey","wallets":[{"address":"wallet1"},{"address":"wallet2"}]}');
p.wallets[1].privateKey = "privatekey2";
p.wallets[1].mnemonic = "mnemonic2";
expect(json.encoder.convert(p), '{"endpoint":"endpoint","appkey":"appkey","wallets":[{"address":"wallet1","privateKey":"privatekey1","mnemonicPhrase":""},{"address":"wallet2","privateKey":"privatekey2","mnemonicPhrase":"mnemonic2"}]}');
});

test('deserialize preferences', () {
Expand All @@ -47,10 +50,33 @@ void main() {
//
// some values
//
p = Preferences.fromJson('{"endpoint":"an endpoint","appkey":"an appkey","wallets":[{"address":"a wallet"}]}');
p = Preferences.fromJson('{"endpoint":"an endpoint","appkey":"an appkey","wallets":[{"address":"a wallet","privateKey":"privatekey","mnemonicPhrase":"mnemonicphrase"}]}');
expect(p.endpoint, "an endpoint");
expect(p.appkey, "an appkey");
expect(p.wallets.length, 1);
expect(p.wallets[0].address, "a wallet");
expect(p.wallets[0].privateKey, "privatekey");
expect(p.wallets[0].mnemonic, "mnemonicphrase");

//
// empty private key, missing mnemonic
//
p = Preferences.fromJson('{"endpoint":"an endpoint","appkey":"an appkey","wallets":[{"address":"a wallet","privateKey":""}]}');
expect(p.endpoint, "an endpoint");
expect(p.appkey, "an appkey");
expect(p.wallets[0].address, "a wallet");
expect(p.wallets[0].privateKey, "");
expect(p.wallets[0].mnemonic, "");

//
// missing private key, empty mnemonic
//
p = Preferences.fromJson('{"endpoint":"an endpoint","appkey":"an appkey","wallets":[{"address":"a wallet","mnemonic":""}]}');
expect(p.endpoint, "an endpoint");
expect(p.appkey, "an appkey");
expect(p.wallets.length, 1); expect(p.wallets[0].address, "a wallet");
expect(p.wallets[0].address, "a wallet");
expect(p.wallets[0].privateKey, "");
expect(p.wallets[0].mnemonic, "");
});

test('deserialize preferences with missing values', () {
Expand Down
7 changes: 6 additions & 1 deletion test/testing_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ const String WALLET2 = "0123456789012345678901234567890123456789";
// print(bytesToHex(random.privateKey));
// print((await random.extractAddress()).hex);
//
const String PRIVATE_KEY1 = "008a2b2d41febc2bef749ecec009b86e5fa18753439b28789658eb7b411397abb6";
const String PRIVATE_KEY1 = "8a2b2d41febc2bef749ecec009b86e5fa18753439b28789658eb7b411397abb6";
const String PRIVATE_KEY2 = "436804c64fea7474fc184d88f8219a3a72c6a9c26321e53babd3c4a8775ed88f";
//
// Derived by mnemonic phrase "alert record income curve mercy tree heavy loan hen recycle mean devote"
//
const String PRIVATE_KEY3 = "c8f12c80b8c0325bb15aa8546f7b0bea133c884da3cfb6f2096368d94192cb37";
//
// Derived by mnemonic phrase "alert record income curve mercy tree heavy loan hen recycle mean devote" #1
//
const String PRIVATE_KEY6 = "82b4cd6699cc1aee53b492598def7833a5ca8aae948f817c325548cb3e62c610";
const String ADDRESS1 = "0xc2a6927e5e2f27e5fc7d2611cb0246fb3151f034";
const String ADDRESS2 = "0x496ef9de509d5d4b3f48f33eb75e55c4b3005dc7";
const String ADDRESS3 = "0x1489a7dd02ca2294ed999cfc175050c852851dec"; // associated to PRIVATE_KEY3
const String ADDRESS4 = "0x00000000219ab540356cbb839cbe05303d7705fa";
const String ADDRESS5 = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
const String ADDRESS6 = "0xb24f4ad87c027f05c58a71eed50193364c1c4a22"; // associated to PRIVATE_KEY6

2 changes: 1 addition & 1 deletion test/ui/add_wallet_dialog_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ void main() {
expect(find.text(LABEL_PRIVATE_KEY_HINT), findsNothing);
});

testWidgets('close add wallet dialog by cancel', (WidgetTester tester) async {
testWidgets('cancel closes add wallet dialog', (WidgetTester tester) async {
await _showDialog(tester);
expect(find.byType(Dialog), findsOneWidget);
await tester.tap(find.descendant(
Expand Down
19 changes: 15 additions & 4 deletions test/ui/edit_private_key_dialog_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ void main() {
home.state.controller + wallet;
tester.state(find.byType(EasyWalletHomePage)).setState(() {}); await tester.pump();

var button = find.descendant(of: find.byKey(Key(ADDRESS3.substring(2))), matching: find.byIcon(Icons.lock_open));

await tester.tap(button);
await tester.pumpAndSettle();
await tester.tap(
find.descendant(of: find.byKey(Key(ADDRESS3.substring(2))), matching: find.byIcon(Icons.lock_open))
); await tester.pumpAndSettle();

return find.byType(Dialog);
}
Expand Down Expand Up @@ -85,6 +84,18 @@ void main() {
expect(btnok.enabled, isTrue);
});

testWidgets('valid mnemonic phrase derives the proper private key', (WidgetTester tester) async {
var dialog = await _showDialog(tester);

var textField = find.descendant(of: dialog, matching: find.byKey(KEY_MNEMONIC_PHRASE));
await tester.enterText(
textField,
LABEL_MNEMONIC_PHRASE_HINT
);
await tester.pumpAndSettle();

expect(find.descendant(of: dialog, matching: find.textContaining(PRIVATE_KEY3)), findsOneWidget);
});

testWidgets('close the dialog when pressing ok', (WidgetTester tester) async {
var dialog = await _showDialog(tester);
Expand Down
Loading

0 comments on commit 26937bc

Please sign in to comment.