Skip to content

Commit fc3cc6e

Browse files
committed
feat: zano hf + bip39
1 parent 787d454 commit fc3cc6e

22 files changed

Lines changed: 224 additions & 36 deletions

cw_monero/pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,8 +545,8 @@ packages:
545545
dependency: "direct main"
546546
description:
547547
path: "impls/monero.dart"
548-
ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
549-
resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
548+
ref: "4f23e050f7dec0bbbbe27c36c8e511bb3f52868a"
549+
resolved-ref: "4f23e050f7dec0bbbbe27c36c8e511bb3f52868a"
550550
url: "https://github.com/mrcyjanek/monero_c.git"
551551
source: git
552552
version: "0.0.0"

cw_monero/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ dependencies:
2626
monero:
2727
git:
2828
url: https://github.com/mrcyjanek/monero_c.git
29-
ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
29+
ref: 4f23e050f7dec0bbbbe27c36c8e511bb3f52868a
3030
path: impls/monero.dart
3131
mutex: ^3.1.0
3232
ledger_flutter_plus: ^1.4.1

cw_wownero/pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@ packages:
449449
dependency: "direct main"
450450
description:
451451
path: "impls/monero.dart"
452-
ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
453-
resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
452+
ref: "4f23e050f7dec0bbbbe27c36c8e511bb3f52868a"
453+
resolved-ref: "4f23e050f7dec0bbbbe27c36c8e511bb3f52868a"
454454
url: "https://github.com/mrcyjanek/monero_c.git"
455455
source: git
456456
version: "0.0.0"

cw_wownero/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies:
2424
monero:
2525
git:
2626
url: https://github.com/mrcyjanek/monero_c.git
27-
ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
27+
ref: 4f23e050f7dec0bbbbe27c36c8e511bb3f52868a
2828
path: impls/monero.dart
2929
mutex: ^3.1.0
3030

cw_zano/lib/api/model/transfer.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,14 @@ class Transfer {
6565
remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<
6666
dynamic>).cast<String>(),
6767
showSender: json['show_sender'] as bool? ?? false,
68-
subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) =>
69-
Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
68+
subtransfers: (json['subtransfers_by_pid'] as List<dynamic>? ?? [])
69+
.whereType<Map<String, dynamic>>()
70+
.expand(
71+
(group) => (group['subtransfers'] as List<dynamic>? ?? [])
72+
.whereType<Map<String, dynamic>>()
73+
.map(Subtransfer.fromJson),
74+
)
75+
.toList(),
7076
timestamp: json['timestamp'] as int? ?? 0,
7177
transferInternalIndex: json['transfer_internal_index'] == null
7278
? 0

cw_zano/lib/bip39_seed.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:bip39/bip39.dart' as bip39;
4+
5+
bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic);
6+
7+
String generateBip39Mnemonic({int strength = 128}) =>
8+
bip39.generateMnemonic(strength: strength);
9+
10+
String getSecretDerivationFromBip39(String mnemonic, {String passphrase = ''}) {
11+
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase);
12+
return Uint8List.fromList(seed.sublist(0, 32)).toHexString();
13+
}
14+
15+
extension on Uint8List {
16+
String toHexString() =>
17+
map((b) => b.toRadixString(16).padLeft(2, '0')).join();
18+
}

cw_zano/lib/zano_wallet.dart

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import 'package:cw_zano/zano_wallet_api.dart';
3333
import 'package:cw_zano/zano_wallet_exceptions.dart';
3434
import 'package:cw_zano/zano_wallet_service.dart';
3535
import 'package:cw_zano/api/model/balance.dart';
36+
import 'package:cw_zano/bip39_seed.dart';
3637

3738
import 'package:mobx/mobx.dart';
3839

@@ -132,14 +133,27 @@ abstract class ZanoWalletBase
132133
final wallet = ZanoWallet(credentials.walletInfo!, await credentials.walletInfo!.getDerivationInfo(), credentials.password!);
133134
await wallet.initWallet();
134135
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
135-
final createWalletResult = await wallet.createWallet(path, credentials.password!);
136+
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
137+
final providedMnemonic =
138+
credentials is ZanoNewWalletCredentials ? credentials.mnemonic : null;
139+
final mnemonic = providedMnemonic ?? generateBip39Mnemonic(strength: strength);
140+
final passphrase = credentials.passphrase ?? '';
141+
final secretDerivation = getSecretDerivationFromBip39(mnemonic, passphrase: passphrase);
142+
final creationTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
143+
final createWalletResult = await wallet.restoreWalletFromDerivations(
144+
path,
145+
credentials.password!,
146+
secretDerivation,
147+
creationTimestamp: creationTimestamp,
148+
);
136149
await wallet.initWallet();
137-
await wallet.parseCreateWalletResult(createWalletResult);
150+
await wallet.setBip39Secrets(mnemonic: mnemonic, creationTimestamp: creationTimestamp);
138151
if (credentials.passphrase != null) {
139152
await wallet.setPassphrase(credentials.passphrase!);
140-
wallet.seed = await createWalletResult.seed(wallet);
141-
wallet.passphrase = await wallet.getPassphrase();
142153
}
154+
await wallet.parseCreateWalletResult(createWalletResult);
155+
wallet.seed = mnemonic;
156+
wallet.passphrase = await wallet.getPassphrase();
143157
await wallet.init(createWalletResult.wi.address);
144158
return wallet;
145159
}
@@ -149,15 +163,33 @@ abstract class ZanoWalletBase
149163
final wallet = ZanoWallet(credentials.walletInfo!, await credentials.walletInfo!.getDerivationInfo(), credentials.password!);
150164
await wallet.initWallet();
151165
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
152-
final createWalletResult = await wallet.restoreWalletFromSeed(
153-
path, credentials.password!, credentials.mnemonic, credentials.passphrase);
166+
final passphrase = credentials.passphrase ?? '';
167+
final CreateWalletResult createWalletResult;
168+
if (isBip39Seed(credentials.mnemonic)) {
169+
final secretDerivation =
170+
getSecretDerivationFromBip39(credentials.mnemonic, passphrase: passphrase);
171+
final creationTimestamp = (credentials.height ?? 0) > 0 ? credentials.height! : 0;
172+
createWalletResult = await wallet.restoreWalletFromDerivations(
173+
path,
174+
credentials.password!,
175+
secretDerivation,
176+
creationTimestamp: creationTimestamp,
177+
);
178+
await wallet.setBip39Secrets(
179+
mnemonic: credentials.mnemonic,
180+
creationTimestamp: creationTimestamp,
181+
);
182+
} else {
183+
createWalletResult = await wallet.restoreWalletFromSeed(
184+
path, credentials.password!, credentials.mnemonic, credentials.passphrase);
185+
}
154186
await wallet.initWallet();
155-
await wallet.parseCreateWalletResult(createWalletResult);
156187
if (credentials.passphrase != null) {
157188
await wallet.setPassphrase(credentials.passphrase!);
158-
wallet.seed = await createWalletResult.seed(wallet);
159-
wallet.passphrase = await wallet.getPassphrase();
160189
}
190+
await wallet.parseCreateWalletResult(createWalletResult);
191+
wallet.seed = isBip39Seed(credentials.mnemonic) ? credentials.mnemonic : await createWalletResult.seed(wallet);
192+
wallet.passphrase = await wallet.getPassphrase();
161193
await wallet.init(createWalletResult.wi.address);
162194
return wallet;
163195
}
@@ -210,6 +242,19 @@ abstract class ZanoWalletBase
210242
transactionHistory.addMany(transactions);
211243
await transactionHistory.save();
212244
}
245+
await _ensureBip39DerivationInfo();
246+
}
247+
248+
Future<void> _ensureBip39DerivationInfo() async {
249+
if (await getBip39Mnemonic() == null) {
250+
return;
251+
}
252+
final di = await walletInfo.getDerivationInfo();
253+
if (di.derivationType == DerivationType.bip39) {
254+
return;
255+
}
256+
di.derivationType = DerivationType.bip39;
257+
await di.save();
213258
}
214259

215260
@override
@@ -305,12 +350,13 @@ abstract class ZanoWalletBase
305350
Future<Map<String, ZanoTransactionInfo>> fetchTransactions() async {
306351
try {
307352
final transfers = <Transfer>[];
353+
var offset = 0;
308354
late GetRecentTxsAndInfoResult result;
309355
do {
310-
result = await getRecentTxsAndInfo(offset: 0, count: _txChunkSize);
311-
// _lastTxIndex += result.transfers.length;
356+
result = await getRecentTxsAndInfo(offset: offset, count: _txChunkSize);
312357
transfers.addAll(result.transfers);
313-
} while (result.lastItemIndex + 1 < result.totalTransfers);
358+
offset = result.lastItemIndex + 1;
359+
} while (offset < result.totalTransfers);
314360
return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
315361
} catch (e) {
316362
printV((e.toString()));

cw_zano/lib/zano_wallet_api.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,25 @@ mixin ZanoWalletApi {
123123
return _setWalletSecret("passphrase", passphrase);
124124
}
125125

126+
Future<String?> getBip39Mnemonic() async {
127+
return await _getWalletSecret("bip39_mnemonic");
128+
}
129+
130+
Future<void> setBip39Secrets({
131+
required String mnemonic,
132+
required int creationTimestamp,
133+
}) async {
134+
final secrets = await _getSecrets();
135+
secrets["bip39_mnemonic"] = mnemonic;
136+
secrets["creation_timestamp"] = creationTimestamp.toString();
137+
await _setSecrets(secrets);
138+
}
139+
126140
Future<String> getSeed() async {
141+
final bip39Mnemonic = await getBip39Mnemonic();
142+
if (bip39Mnemonic != null) {
143+
return bip39Mnemonic;
144+
}
127145
final passphrase = await getPassphrase();
128146
final respStr = await invokeMethod("get_restore_info", {"seed_password": passphrase??""});
129147
final resp = convert.json.decode(respStr);
@@ -319,6 +337,43 @@ mixin ZanoWalletApi {
319337
return result;
320338
}
321339

340+
Future<CreateWalletResult> restoreWalletFromDerivations(
341+
String path,
342+
String password,
343+
String secretDerivation, {
344+
bool isAuditable = false,
345+
required int creationTimestamp,
346+
}) async {
347+
printV('restore_wallet_from_derivations path $path');
348+
final params = jsonEncode({
349+
"creation_timestamp": creationTimestamp,
350+
"is_auditable": isAuditable,
351+
"pass": password,
352+
"path": path,
353+
"secret_derivation": secretDerivation,
354+
});
355+
final json = await callSyncMethod('restore_from_derivations', 0, params);
356+
final map = jsonDecode(json) as Map<String, dynamic>?;
357+
if (map?['error'] != null) {
358+
final code = map!['error']!['code'] ?? '';
359+
final message = map['error']!['message'] ?? '';
360+
if (code == Consts.errorWrongSeed) {
361+
throw RestoreFromSeedsException(
362+
'Error restoring wallet\nPlease check the seed words are correct. Additionally, if you created this wallet with a passphrase please add it under the Advanced Settings page.');
363+
} else if (code == Consts.errorAlreadyExists) {
364+
throw RestoreFromSeedsException('Error restoring wallet, already exists');
365+
}
366+
throw RestoreFromSeedsException('Error restoring wallet, $message ($code)');
367+
}
368+
if (map?['result'] == null) {
369+
throw RestoreFromSeedsException('Error restoring wallet, empty response');
370+
}
371+
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
372+
openWalletCache[path] = result;
373+
printV('restore_wallet_from_derivations ${result.name} ${result.wi.address}');
374+
return result;
375+
}
376+
322377
Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
323378
printV('restore_wallet path $path');
324379
final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');

cw_zano/lib/zano_wallet_service.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ import 'package:hive/hive.dart';
1414
import 'package:monero/zano.dart' as zano;
1515

1616
class ZanoNewWalletCredentials extends WalletCredentials {
17-
ZanoNewWalletCredentials({required String name, String? password, required String? passphrase}) : super(name: name, password: password, passphrase: passphrase);
17+
ZanoNewWalletCredentials({
18+
required String name,
19+
String? password,
20+
required String? passphrase,
21+
this.mnemonic,
22+
}) : super(name: name, password: password, passphrase: passphrase);
23+
24+
final String? mnemonic;
1825
}
1926

2027
class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials {

cw_zano/pubspec.lock

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ packages:
5050
url: "https://github.com/cake-tech/bech32.git"
5151
source: git
5252
version: "0.2.2"
53+
bip39:
54+
dependency: "direct main"
55+
description:
56+
name: bip39
57+
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
58+
url: "https://pub.dev"
59+
source: hosted
60+
version: "1.0.6"
5361
blockchain_utils:
5462
dependency: transitive
5563
description:
@@ -298,6 +306,14 @@ packages:
298306
url: "https://pub.dev"
299307
source: hosted
300308
version: "2.3.2"
309+
hex:
310+
dependency: transitive
311+
description:
312+
name: hex
313+
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
314+
url: "https://pub.dev"
315+
source: hosted
316+
version: "0.2.0"
301317
hive:
302318
dependency: "direct dev"
303319
description:
@@ -454,8 +470,8 @@ packages:
454470
dependency: "direct main"
455471
description:
456472
path: "impls/monero.dart"
457-
ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
458-
resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
473+
ref: "4f23e050f7dec0bbbbe27c36c8e511bb3f52868a"
474+
resolved-ref: "4f23e050f7dec0bbbbe27c36c8e511bb3f52868a"
459475
url: "https://github.com/mrcyjanek/monero_c.git"
460476
source: git
461477
version: "0.0.0"

0 commit comments

Comments
 (0)