feat: zano hf + bip39 (#3359)
What changed, and why it matters
This commit adds BIP39 seed phrase support for the Zano cryptocurrency in Cake Wallet, alongside a Zano hard-fork update. It changes how Zano wallets are created, restored, opened, renamed, and saved, and stores the seed/passphrase in encrypted sidecar files. The change also bumps the underlying monero_c library to a newer commit. There is no vendor statement that this fixes a security vulnerability; it reads as a feature/upgrade patch. Some implementation choices (custom key reduction, hard-coded derivation path, fallback to native seed, and sidecar key management) introduce areas where key-handling mistakes could occur, but the diff itself does not show an obvious exploitable bug.
Treat this as a high-touch key-management change rather than a confirmed vulnerability. Review the custom _reduceECKey implementation against standard Ed25519 clamping, verify the BIP32 path and passphrase handling match Zano's hard-fork requirements, audit the sidecar encryption and backup behavior, and confirm the monero_c dependency bump does not introduce breaking or security-relevant changes. No immediate patch or CVE assignment is indicated by the commit alone.
Security signals we found
New BIP39/BIP32 key derivation code with custom Ed25519 private-key reduction
Encrypted sidecar key file introduced for Zano seed/passphrase persistence
Wallet rename/delete logic rewritten to copy/delete additional sidecar files
Fallback code paths that may use native Zano seed when BIP39 seed is unavailable
Dependency bump for monero_c library (no changelog or security note supplied)
Evidence from the diff
The patch enables BIP39/BIP32 derivation for Zano using path m/44’/128’/0’/0/0, adds a custom Ed25519 curve-order reduction on the BIP32 private key, and stores the BIP39 mnemonic and passphrase in encrypted .cw.keys sidecars. It changes Zano wallet lifecycle methods (create/restore/open/rename/save) to use these new credentials and sidecars, updates transaction transfer parsing to read subtransfers_by_pid, and bumps the monero_c dependency across Monero, Wownero, and Zano packages. The commit does not describe a security fix, and no CVE or advisory is referenced.
Changed components
cw_zano/lib/bip39_seed.dartcw_zano/lib/zano_wallet.dartcw_zano/lib/zano_wallet_api.dartcw_zano/lib/zano_wallet_service.dartcw_zano/lib/zano_utils.dartcw_zano/lib/api/model/transfer.dartlib/zano/cw_zano.dartlib/view_model/wallet_restore_view_model.dartlib/view_model/wallet_new_vm.dartlib/core/wallet_creation_service.dartcw_monero/pubspec.yamlcw_wownero/pubspec.yamlcw_zano/pubspec.yamlpubspec.lockpubspec_overrides.yamlscripts/prepare_moneroc.shInspect captured patch +1072 / −104
diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml
index 3f1c10f..04d3f39 100644
--- a/cw_monero/pubspec.yaml
+++ b/cw_monero/pubspec.yaml
@@ -26,7 +26,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c.git
- ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
+ ref: cd4fe366cc8d3c188c91de279f43b29c0e044b15
path: impls/monero.dart
mutex: ^3.1.0
ledger_flutter_plus: ^1.4.1
diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml
index 6f99024..da8f909 100644
--- a/cw_wownero/pubspec.yaml
+++ b/cw_wownero/pubspec.yaml
@@ -24,7 +24,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c.git
- ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
+ ref: cd4fe366cc8d3c188c91de279f43b29c0e044b15
path: impls/monero.dart
mutex: ^3.1.0
diff --git a/cw_zano/lib/api/model/transfer.dart b/cw_zano/lib/api/model/transfer.dart
index 2d9b51d..37a7308 100644
--- a/cw_zano/lib/api/model/transfer.dart
+++ b/cw_zano/lib/api/model/transfer.dart
@@ -66,8 +66,13 @@ class Transfer {
? []
: (json['remote_aliases'] as List<dynamic>).cast<String>(),
showSender: json['show_sender'] as bool? ?? false,
- subtransfers: (json['subtransfers'] as List<dynamic>? ?? [])
- .map((e) => Subtransfer.fromJson(e as Map<String, dynamic>))
+ subtransfers: (json['subtransfers_by_pid'] as List<dynamic>? ?? [])
+ .whereType<Map<String, dynamic>>()
+ .expand(
+ (group) => (group['subtransfers'] as List<dynamic>? ?? [])
+ .whereType<Map<String, dynamic>>()
+ .map(Subtransfer.fromJson),
+ )
.toList(),
timestamp: json['timestamp'] as int? ?? 0,
transferInternalIndex: json['transfer_internal_index'] == null
diff --git a/cw_zano/lib/bip39_seed.dart b/cw_zano/lib/bip39_seed.dart
new file mode 100644
index 0000000..b054a5d
--- /dev/null
+++ b/cw_zano/lib/bip39_seed.dart
@@ -0,0 +1,60 @@
+import 'dart:typed_data';
+
+import 'package:bip32/bip32.dart' as bip32;
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:bip39/src/wordlists/english.dart' as bip39_english;
+
+bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic);
+
+List<String> get bip39EnglishWords => List<String>.from(bip39_english.WORDLIST);
+
+String generateBip39Mnemonic({int strength = 128}) =>
+ bip39.generateMnemonic(strength: strength);
+
+String getSecretDerivationFromBip39(String mnemonic, {String passphrase = ''}) {
+ final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase);
+
+ final bip32KeyPair = bip32.BIP32.fromSeed(seed).derivePath("m/44'/128'/0'/0/0");
+
+ return _reduceECKey(bip32KeyPair.privateKey!).toHexString();
+}
+
+const _ed25519CurveOrder = "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED";
+
+Uint8List _reduceECKey(Uint8List buffer) {
+ final curveOrder = BigInt.parse(_ed25519CurveOrder, radix: 16);
+ final bigNumber = _readBytes(buffer);
+
+ var result = bigNumber % curveOrder;
+
+ final resultBuffer = Uint8List(32);
+ for (var i = 0; i < 32; i++) {
+ resultBuffer[i] = (result & BigInt.from(0xff)).toInt();
+ result = result >> 8;
+ }
+
+ return resultBuffer;
+}
+
+/// Read BigInt from a little-endian Uint8List
+/// From https://github.com/dart-lang/sdk/issues/32803#issuecomment-387405784
+BigInt _readBytes(Uint8List bytes) {
+ BigInt read(int start, int end) {
+ if (end - start <= 4) {
+ var result = 0;
+ for (int i = end - 1; i >= start; i--) {
+ result = result * 256 + bytes[i];
+ }
+ return BigInt.from(result);
+ }
+ final mid = start + ((end - start) >> 1);
+ return read(start, mid) + read(mid, end) * (BigInt.one << ((mid - start) * 8));
+ }
+
+ return read(0, bytes.length);
+}
+
+extension on Uint8List {
+ String toHexString() =>
+ map((b) => b.toRadixString(16).padLeft(2, '0')).join();
+}
diff --git a/cw_zano/lib/get_height_by_data.txt b/cw_zano/lib/get_height_by_data.txt
new file mode 100644
index 0000000..43df05f
--- /dev/null
+++ b/cw_zano/lib/get_height_by_data.txt
@@ -0,0 +1,4 @@
+NODE=http://37.27.100.59:10500/json_rpc
+HEIGHT=$(curl -s -X POST $NODE -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":"0","method":"getinfo"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['height'])")
+echo "_Checkpoint($(curl -s -X POST $NODE -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":"0","method":"getblockheaderbyheight","params":{"height":1}}' | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['block_header']['timestamp'])"), 1),"
+for i in $(seq 1 $((HEIGHT/10000))); do echo "_Checkpoint($(curl -s -X POST $NODE -H 'Content-Type: application/json' -d "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getblockheaderbyheight\",\"params\":{\"height\":${i}0000}}" | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['block_header']['timestamp'])"), ${i}0000),"; done
diff --git a/cw_zano/lib/get_height_by_date.dart b/cw_zano/lib/get_height_by_date.dart
new file mode 100644
index 0000000..83cef73
--- /dev/null
+++ b/cw_zano/lib/get_height_by_date.dart
@@ -0,0 +1,467 @@
+class ZanoHeight {
+ static final List<_Checkpoint> _points = [
+ _Checkpoint(1557342384, 1),
+ _Checkpoint(1557906733, 10000),
+ _Checkpoint(1558516097, 20000),
+ _Checkpoint(1559119125, 30000),
+ _Checkpoint(1559741805, 40000),
+ _Checkpoint(1560341175, 50000),
+ _Checkpoint(1560948480, 60000),
+ _Checkpoint(1561555965, 70000),
+ _Checkpoint(1562142135, 80000),
+ _Checkpoint(1562749400, 90000),
+ _Checkpoint(1563354315, 100000),
+ _Checkpoint(1563958673, 110000),
+ _Checkpoint(1564559835, 120000),
+ _Checkpoint(1565159325, 130000),
+ _Checkpoint(1565772195, 140000),
+ _Checkpoint(1566389235, 150000),
+ _Checkpoint(1566992793, 160000),
+ _Checkpoint(1567597678, 170000),
+ _Checkpoint(1568204955, 180000),
+ _Checkpoint(1568812530, 190000),
+ _Checkpoint(1569418329, 200000),
+ _Checkpoint(1570035660, 210000),
+ _Checkpoint(1570641964, 220000),
+ _Checkpoint(1571242140, 230000),
+ _Checkpoint(1571841282, 240000),
+ _Checkpoint(1572443911, 250000),
+ _Checkpoint(1573049280, 260000),
+ _Checkpoint(1573653255, 270000),
+ _Checkpoint(1574251680, 280000),
+ _Checkpoint(1574854984, 290000),
+ _Checkpoint(1575459060, 300000),
+ _Checkpoint(1576064640, 310000),
+ _Checkpoint(1576665225, 320000),
+ _Checkpoint(1577271045, 330000),
+ _Checkpoint(1577867835, 340000),
+ _Checkpoint(1578470370, 350000),
+ _Checkpoint(1579078170, 360000),
+ _Checkpoint(1579674765, 370000),
+ _Checkpoint(1580277860, 380000),
+ _Checkpoint(1580878155, 390000),
+ _Checkpoint(1581482220, 400000),
+ _Checkpoint(1582087577, 410000),
+ _Checkpoint(1582689270, 420000),
+ _Checkpoint(1583284005, 430000),
+ _Checkpoint(1583880062, 440000),
+ _Checkpoint(1584480692, 450000),
+ _Checkpoint(1585085520, 460000),
+ _Checkpoint(1585694261, 470000),
+ _Checkpoint(1586292210, 480000),
+ _Checkpoint(1586893822, 490000),
+ _Checkpoint(1587498656, 500000),
+ _Checkpoint(1588099364, 510000),
+ _Checkpoint(1588696710, 520000),
+ _Checkpoint(1589300843, 530000),
+ _Checkpoint(1589904930, 540000),
+ _Checkpoint(1590499185, 550000),
+ _Checkpoint(1591111133, 560000),
+ _Checkpoint(1591715587, 570000),
+ _Checkpoint(1592316375, 580000),
+ _Checkpoint(1592918012, 590000),
+ _Checkpoint(1593521050, 600000),
+ _Checkpoint(1594123886, 610000),
+ _Checkpoint(1594725660, 620000),
+ _Checkpoint(1595326710, 630000),
+ _Checkpoint(1595929088, 640000),
+ _Checkpoint(1596535020, 650000),
+ _Checkpoint(1597132395, 660000),
+ _Checkpoint(1597740870, 670000),
+ _Checkpoint(1598339827, 680000),
+ _Checkpoint(1598945010, 690000),
+ _Checkpoint(1599547530, 700000),
+ _Checkpoint(1600153050, 710000),
+ _Checkpoint(1600754010, 720000),
+ _Checkpoint(1601355810, 730000),
+ _Checkpoint(1601956635, 740000),
+ _Checkpoint(1602558615, 750000),
+ _Checkpoint(1603155648, 760000),
+ _Checkpoint(1603759399, 770000),
+ _Checkpoint(1604365586, 780000),
+ _Checkpoint(1604966602, 790000),
+ _Checkpoint(1605564777, 800000),
+ _Checkpoint(1606168023, 810000),
+ _Checkpoint(1606772409, 820000),
+ _Checkpoint(1607377110, 830000),
+ _Checkpoint(1607988084, 840000),
+ _Checkpoint(1608593385, 850000),
+ _Checkpoint(1609196310, 860000),
+ _Checkpoint(1609793426, 870000),
+ _Checkpoint(1610398975, 880000),
+ _Checkpoint(1610999055, 890000),
+ _Checkpoint(1611601290, 900000),
+ _Checkpoint(1612208050, 910000),
+ _Checkpoint(1612805250, 920000),
+ _Checkpoint(1613409645, 930000),
+ _Checkpoint(1614003255, 940000),
+ _Checkpoint(1614600803, 950000),
+ _Checkpoint(1615202091, 960000),
+ _Checkpoint(1615802123, 970000),
+ _Checkpoint(1616414192, 980000),
+ _Checkpoint(1617015705, 990000),
+ _Checkpoint(1617613785, 1000000),
+ _Checkpoint(1618218467, 1010000),
+ _Checkpoint(1618821464, 1020000),
+ _Checkpoint(1619424052, 1030000),
+ _Checkpoint(1620027510, 1040000),
+ _Checkpoint(1620630585, 1050000),
+ _Checkpoint(1621232355, 1060000),
+ _Checkpoint(1621832220, 1070000),
+ _Checkpoint(1622426032, 1080000),
+ _Checkpoint(1623041250, 1090000),
+ _Checkpoint(1623644732, 1100000),
+ _Checkpoint(1624244801, 1110000),
+ _Checkpoint(1624848450, 1120000),
+ _Checkpoint(1625449290, 1130000),
+ _Checkpoint(1626049974, 1140000),
+ _Checkpoint(1626656033, 1150000),
+ _Checkpoint(1627255830, 1160000),
+ _Checkpoint(1627855505, 1170000),
+ _Checkpoint(1628464065, 1180000),
+ _Checkpoint(1629064455, 1190000),
+ _Checkpoint(1629665715, 1200000),
+ _Checkpoint(1630268475, 1210000),
+ _Checkpoint(1630870911, 1220000),
+ _Checkpoint(1631475471, 1230000),
+ _Checkpoint(1632071441, 1240000),
+ _Checkpoint(1632677576, 1250000),
+ _Checkpoint(1633286445, 1260000),
+ _Checkpoint(1633884645, 1270000),
+ _Checkpoint(1634480355, 1280000),
+ _Checkpoint(1635077039, 1290000),
+ _Checkpoint(1635679740, 1300000),
+ _Checkpoint(1636284345, 1310000),
+ _Checkpoint(1636882865, 1320000),
+ _Checkpoint(1637482276, 1330000),
+ _Checkpoint(1638082296, 1340000),
+ _Checkpoint(1638682601, 1350000),
+ _Checkpoint(1639287225, 1360000),
+ _Checkpoint(1639884465, 1370000),
+ _Checkpoint(1640484903, 1380000),
+ _Checkpoint(1641084033, 1390000),
+ _Checkpoint(1641688500, 1400000),
+ _Checkpoint(1642286685, 1410000),
+ _Checkpoint(1642886842, 1420000),
+ _Checkpoint(1643493960, 1430000),
+ _Checkpoint(1644093045, 1440000),
+ _Checkpoint(1644691833, 1450000),
+ _Checkpoint(1645291245, 1460000),
+ _Checkpoint(1645892190, 1470000),
+ _Checkpoint(1646489895, 1480000),
+ _Checkpoint(1647089648, 1490000),
+ _Checkpoint(1647694665, 1500000),
+ _Checkpoint(1648293585, 1510000),
+ _Checkpoint(1648893525, 1520000),
+ _Checkpoint(1649497306, 1530000),
+ _Checkpoint(1650098656, 1540000),
+ _Checkpoint(1650694980, 1550000),
+ _Checkpoint(1651303650, 1560000),
+ _Checkpoint(1651899285, 1570000),
+ _Checkpoint(1652501596, 1580000),
+ _Checkpoint(1653101220, 1590000),
+ _Checkpoint(1653700166, 1600000),
+ _Checkpoint(1654302094, 1610000),
+ _Checkpoint(1654906575, 1620000),
+ _Checkpoint(1655510644, 1630000),
+ _Checkpoint(1656108615, 1640000),
+ _Checkpoint(1656712545, 1650000),
+ _Checkpoint(1657313460, 1660000),
+ _Checkpoint(1657911515, 1670000),
+ _Checkpoint(1658515717, 1680000),
+ _Checkpoint(1659121230, 1690000),
+ _Checkpoint(1659722213, 1700000),
+ _Checkpoint(1660318875, 1710000),
+ _Checkpoint(1660921522, 1720000),
+ _Checkpoint(1661518436, 1730000),
+ _Checkpoint(1662118050, 1740000),
+ _Checkpoint(1662717982, 1750000),
+ _Checkpoint(1663306425, 1760000),
+ _Checkpoint(1663904520, 1770000),
+ _Checkpoint(1664511195, 1780000),
+ _Checkpoint(1665113460, 1790000),
+ _Checkpoint(1665705148, 1800000),
+ _Checkpoint(1666306807, 1810000),
+ _Checkpoint(1666909050, 1820000),
+ _Checkpoint(1667510577, 1830000),
+ _Checkpoint(1668111697, 1840000),
+ _Checkpoint(1668711150, 1850000),
+ _Checkpoint(1669309785, 1860000),
+ _Checkpoint(1669910715, 1870000),
+ _Checkpoint(1670511885, 1880000),
+ _Checkpoint(1671111596, 1890000),
+ _Checkpoint(1671713348, 1900000),
+ _Checkpoint(1672312933, 1910000),
+ _Checkpoint(1672913610, 1920000),
+ _Checkpoint(1673517614, 1930000),
+ _Checkpoint(1674116655, 1940000),
+ _Checkpoint(1674717105, 1950000),
+ _Checkpoint(1675316550, 1960000),
+ _Checkpoint(1675914240, 1970000),
+ _Checkpoint(1676519608, 1980000),
+ _Checkpoint(1677113500, 1990000),
+ _Checkpoint(1677715095, 2000000),
+ _Checkpoint(1678316835, 2010000),
+ _Checkpoint(1678912224, 2020000),
+ _Checkpoint(1679513175, 2030000),
+ _Checkpoint(1680112070, 2040000),
+ _Checkpoint(1680711218, 2050000),
+ _Checkpoint(1681313478, 2060000),
+ _Checkpoint(1681909155, 2070000),
+ _Checkpoint(1682507671, 2080000),
+ _Checkpoint(1683107382, 2090000),
+ _Checkpoint(1683712020, 2100000),
+ _Checkpoint(1684311975, 2110000),
+ _Checkpoint(1684915377, 2120000),
+ _Checkpoint(1685514352, 2130000),
+ _Checkpoint(1686112770, 2140000),
+ _Checkpoint(1686710430, 2150000),
+ _Checkpoint(1687309584, 2160000),
+ _Checkpoint(1687916053, 2170000),
+ _Checkpoint(1688521450, 2180000),
+ _Checkpoint(1689118965, 2190000),
+ _Checkpoint(1689719584, 2200000),
+ _Checkpoint(1690321515, 2210000),
+ _Checkpoint(1690920915, 2220000),
+ _Checkpoint(1691524920, 2230000),
+ _Checkpoint(1692120135, 2240000),
+ _Checkpoint(1692715440, 2250000),
+ _Checkpoint(1693313578, 2260000),
+ _Checkpoint(1693915410, 2270000),
+ _Checkpoint(1694516824, 2280000),
+ _Checkpoint(1695114075, 2290000),
+ _Checkpoint(1695712485, 2300000),
+ _Checkpoint(1696314266, 2310000),
+ _Checkpoint(1696913190, 2320000),
+ _Checkpoint(1697513447, 2330000),
+ _Checkpoint(1698115590, 2340000),
+ _Checkpoint(1698715860, 2350000),
+ _Checkpoint(1699318665, 2360000),
+ _Checkpoint(1699918515, 2370000),
+ _Checkpoint(1700510340, 2380000),
+ _Checkpoint(1701107490, 2390000),
+ _Checkpoint(1701715953, 2400000),
+ _Checkpoint(1702319655, 2410000),
+ _Checkpoint(1702921425, 2420000),
+ _Checkpoint(1703518740, 2430000),
+ _Checkpoint(1704117450, 2440000),
+ _Checkpoint(1704715350, 2450000),
+ _Checkpoint(1705314150, 2460000),
+ _Checkpoint(1705911585, 2470000),
+ _Checkpoint(1706513190, 2480000),
+ _Checkpoint(1707111871, 2490000),
+ _Checkpoint(1707712515, 2500000),
+ _Checkpoint(1708316785, 2510000),
+ _Checkpoint(1708917275, 2520000),
+ _Checkpoint(1709515140, 2530000),
+ _Checkpoint(1710115320, 2540000),
+ _Checkpoint(1710718811, 2550000),
+ _Checkpoint(1711340233, 2560000),
+ _Checkpoint(1711937827, 2570000),
+ _Checkpoint(1712536797, 2580000),
+ _Checkpoint(1713132840, 2590000),
+ _Checkpoint(1713732780, 2600000),
+ _Checkpoint(1714330320, 2610000),
+ _Checkpoint(1714933845, 2620000),
+ _Checkpoint(1715536185, 2630000),
+ _Checkpoint(1716146082, 2640000),
+ _Checkpoint(1716742155, 2650000),
+ _Checkpoint(1717343205, 2660000),
+ _Checkpoint(1717937699, 2670000),
+ _Checkpoint(1718535495, 2680000),
+ _Checkpoint(1719141615, 2690000),
+ _Checkpoint(1719744075, 2700000),
+ _Checkpoint(1720336724, 2710000),
+ _Checkpoint(1720941803, 2720000),
+ _Checkpoint(1721539733, 2730000),
+ _Checkpoint(1722139174, 2740000),
+ _Checkpoint(1722737769, 2750000),
+ _Checkpoint(1723336755, 2760000),
+ _Checkpoint(1723937074, 2770000),
+ _Checkpoint(1724533860, 2780000),
+ _Checkpoint(1725126437, 2790000),
+ _Checkpoint(1725728340, 2800000),
+ _Checkpoint(1726330290, 2810000),
+ _Checkpoint(1726931550, 2820000),
+ _Checkpoint(1727531335, 2830000),
+ _Checkpoint(1728132893, 2840000),
+ _Checkpoint(1728731641, 2850000),
+ _Checkpoint(1729328707, 2860000),
+ _Checkpoint(1729927125, 2870000),
+ _Checkpoint(1730525159, 2880000),
+ _Checkpoint(1731124123, 2890000),
+ _Checkpoint(1731728397, 2900000),
+ _Checkpoint(1732328405, 2910000),
+ _Checkpoint(1732929444, 2920000),
+ _Checkpoint(1733527918, 2930000),
+ _Checkpoint(1734125775, 2940000),
+ _Checkpoint(1734721755, 2950000),
+ _Checkpoint(1735326599, 2960000),
+ _Checkpoint(1735919505, 2970000),
+ _Checkpoint(1736520114, 2980000),
+ _Checkpoint(1737119579, 2990000),
+ _Checkpoint(1737724515, 3000000),
+ _Checkpoint(1738328220, 3010000),
+ _Checkpoint(1738929119, 3020000),
+ _Checkpoint(1739527564, 3030000),
+ _Checkpoint(1740125219, 3040000),
+ _Checkpoint(1740726039, 3050000),
+ _Checkpoint(1741327110, 3060000),
+ _Checkpoint(1741927857, 3070000),
+ _Checkpoint(1742530920, 3080000),
+ _Checkpoint(1743149987, 3090000),
+ _Checkpoint(1743747268, 3100000),
+ _Checkpoint(1744349415, 3110000),
+ _Checkpoint(1744944112, 3120000),
+ _Checkpoint(1745543580, 3130000),
+ _Checkpoint(1746142732, 3140000),
+ _Checkpoint(1746742705, 3150000),
+ _Checkpoint(1747344191, 3160000),
+ _Checkpoint(1747946610, 3170000),
+ _Checkpoint(1748545387, 3180000),
+ _Checkpoint(1749144165, 3190000),
+ _Checkpoint(1749742950, 3200000),
+ _Checkpoint(1750343363, 3210000),
+ _Checkpoint(1750946445, 3220000),
+ _Checkpoint(1751545747, 3230000),
+ _Checkpoint(1752144620, 3240000),
+ _Checkpoint(1752746280, 3250000),
+ _Checkpoint(1753343664, 3260000),
+ _Checkpoint(1753948349, 3270000),
+ _Checkpoint(1754547975, 3280000),
+ _Checkpoint(1755145610, 3290000),
+ _Checkpoint(1755744210, 3300000),
+ _Checkpoint(1756341045, 3310000),
+ _Checkpoint(1756940460, 3320000),
+ _Checkpoint(1757537565, 3330000),
+ _Checkpoint(1758141330, 3340000),
+ _Checkpoint(1758742563, 3350000),
+ _Checkpoint(1759340190, 3360000),
+ _Checkpoint(1759941840, 3370000),
+ _Checkpoint(1760542420, 3380000),
+ _Checkpoint(1761144358, 3390000),
+ _Checkpoint(1761745435, 3400000),
+ _Checkpoint(1762345095, 3410000),
+ _Checkpoint(1762942865, 3420000),
+ _Checkpoint(1763540460, 3430000),
+ _Checkpoint(1764144321, 3440000),
+ _Checkpoint(1764745065, 3450000),
+ _Checkpoint(1765347169, 3460000),
+ _Checkpoint(1765944370, 3470000),
+ _Checkpoint(1766548020, 3480000),
+ _Checkpoint(1767144555, 3490000),
+ _Checkpoint(1767748830, 3500000),
+ _Checkpoint(1768350722, 3510000),
+ _Checkpoint(1768945425, 3520000),
+ _Checkpoint(1769545406, 3530000),
+ _Checkpoint(1770144700, 3540000),
+ _Checkpoint(1770745380, 3550000),
+ _Checkpoint(1771345176, 3560000),
+ _Checkpoint(1771943526, 3570000),
+ _Checkpoint(1772545646, 3580000),
+ _Checkpoint(1773146830, 3590000),
+ _Checkpoint(1773749868, 3600000),
+ _Checkpoint(1774347077, 3610000),
+ _Checkpoint(1774950120, 3620000),
+ _Checkpoint(1775548635, 3630000),
+ _Checkpoint(1776148320, 3640000),
+ _Checkpoint(1776750266, 3650000),
+ _Checkpoint(1777351110, 3660000),
+ _Checkpoint(1777953826, 3670000),
+ _Checkpoint(1778551170, 3680000),
+ _Checkpoint(1779153960, 3690000),
+ _Checkpoint(1779758055, 3700000),
+ _Checkpoint(1780362465, 3710000),
+ _Checkpoint(1780966500, 3720000),
+ _Checkpoint(1781563275, 3730000),
+ _Checkpoint(1782174166, 3740000),
+ _Checkpoint(1782775815, 3750000),
+ _Checkpoint(1783378725, 3760000),
+ _Checkpoint(1783971435, 3770000),
+ _Checkpoint(1784575305, 3780000),
+ _Checkpoint(1785177870, 3790000),
+ _Checkpoint(1785774750, 3800000),
+ _Checkpoint(1786377455, 3810000),
+ _Checkpoint(1786978902, 3820000),
+ ];
+
+ static int getBlockHeightByTime(DateTime time) {
+ final t = time.toUtc().millisecondsSinceEpoch;
+
+ if (_points.isEmpty) return 0;
+
+ if (t <= _points.first.timeMs) {
+ return 1;
+ }
+
+ for (int i = 0; i < _points.length - 1; i++) {
+ final p0 = _points[i];
+ final p1 = _points[i + 1];
+
+ if (t >= p0.timeMs && t < p1.timeMs) {
+ return _interpolate(
+ p0.height,
+ p1.height,
+ p0.timeMs,
+ p1.timeMs,
+ t,
+ );
+ }
+ }
+
+ final last = _points[_points.length - 1];
+ final prev = _points[_points.length - 2];
+
+ final avgBlockTimeMs = (last.timeMs - prev.timeMs) / (last.height - prev.height);
+
+ final blocksSinceLast = ((t - last.timeMs) / avgBlockTimeMs).floor();
+
+ return last.height + blocksSinceLast;
+ }
+
+ static DateTime getTimeByBlockHeight(int height) {
+ if (_points.isEmpty) return DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
+ if (height <= 1) {
+ return DateTime.fromMillisecondsSinceEpoch(_points.first.timeMs, isUtc: true);
+ }
+
+ for (int i = 0; i < _points.length - 1; i++) {
+ final p0 = _points[i];
+ final p1 = _points[i + 1];
+ if (height >= p0.height && height < p1.height) {
+ final t = _interpolate(
+ p0.timeMs,
+ p1.timeMs,
+ p0.height,
+ p1.height,
+ height,
+ );
+ return DateTime.fromMillisecondsSinceEpoch(t, isUtc: true);
+ }
+ }
+
+ final last = _points[_points.length - 1];
+ final prev = _points[_points.length - 2];
+ final avgBlockTimeMs = (last.timeMs - prev.timeMs) / (last.height - prev.height);
+ final msSinceLast = ((height - last.height) * avgBlockTimeMs).floor();
+ return DateTime.fromMillisecondsSinceEpoch(last.timeMs + msSinceLast, isUtc: true);
+ }
+
+ static int _interpolate(
+ int h0,
+ int h1,
+ int t0,
+ int t1,
+ int t,
+ ) {
+ final ratio = (t - t0) / (t1 - t0);
+ return h0 + ((h1 - h0) * ratio).floor();
+ }
+}
+
+class _Checkpoint {
+ final int timeMs;
+ final int height;
+
+ _Checkpoint(int timeSec, this.height) : timeMs = timeSec * 1000;
+}
diff --git a/cw_zano/lib/zano_utils.dart b/cw_zano/lib/zano_utils.dart
index f39f204..72b07b7 100644
--- a/cw_zano/lib/zano_utils.dart
+++ b/cw_zano/lib/zano_utils.dart
@@ -1,9 +1,18 @@
import 'dart:convert';
-import 'package:monero/zano.dart' as zano;
import 'package:cw_zano/api/model/get_address_info_result.dart';
+import 'package:cw_zano/get_height_by_date.dart';
+import 'package:monero/zano.dart' as zano;
class ZanoUtils {
+ static int heightByDate(DateTime date) =>
+ ZanoHeight.getBlockHeightByTime(date.toUtc().subtract(const Duration(days: 1)));
+
+ static int creationTimestampFromHeight(int height) {
+ if (height <= 0) return 0;
+ return ZanoHeight.getTimeByBlockHeight(height).millisecondsSinceEpoch ~/ 1000;
+ }
+
static bool validateAddress(String address) {
try {
final result = GetAddressInfoResult.fromJson(
diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart
index 8b45b14..df5ceff 100644
--- a/cw_zano/lib/zano_wallet.dart
+++ b/cw_zano/lib/zano_wallet.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:convert';
import 'dart:core';
import 'dart:io';
import 'dart:math';
@@ -6,6 +7,7 @@ import 'dart:math';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
@@ -15,6 +17,7 @@ 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_keys_file.dart';
import 'package:cw_core/zano_asset.dart';
import 'package:cw_zano/api/model/create_wallet_result.dart';
import 'package:cw_zano/api/model/destination.dart';
@@ -32,7 +35,10 @@ import 'package:cw_zano/zano_wallet_addresses.dart';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:cw_zano/zano_wallet_exceptions.dart';
import 'package:cw_zano/zano_wallet_service.dart';
+import 'package:cw_zano/zano_utils.dart';
import 'package:cw_zano/api/model/balance.dart';
+import 'package:cw_zano/bip39_seed.dart';
+import 'package:path/path.dart' as p;
import 'package:mobx/mobx.dart';
@@ -42,10 +48,12 @@ class ZanoWallet = ZanoWalletBase with _$ZanoWallet;
abstract class ZanoWalletBase
extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
- with Store, ZanoWalletApi {
+ with Store, ZanoWalletApi, WalletKeysFile {
static const int _autoSaveIntervalSeconds = 30;
static const int _pollIntervalMilliseconds = 5000;
static const int _maxLoadAssetsRetries = 5;
+ static const int _walletStateReady = 2;
+ static const String _cakeKeysFileSuffix = '.cw.keys';
@override
void setPassword(String password) {
@@ -54,10 +62,23 @@ abstract class ZanoWalletBase
}
String _password;
+ final EncryptionFileUtils _encryptionFileUtils;
+ bool _didSyncSecrets = false;
@override
String get password => _password;
+ @override
+ Future<String> makeKeysFilePath() async => "${await makePath()}$_cakeKeysFileSuffix";
+
+ @override
+ WalletKeysData get walletKeysData => WalletKeysData(
+ mnemonic: _hasSeed ? seed : null,
+ passphrase: (passphrase ?? '').isEmpty ? null : passphrase,
+ );
+
+ bool get _hasSeed => seed.trim().isNotEmpty;
+
@override
Future<String> signMessage(String message, {String? address = null}) =>
super.signMessage(message, address: address);
@@ -105,7 +126,8 @@ abstract class ZanoWalletBase
/// number of transactions in each request
static final int _txChunkSize = (pow(2, 32) - 1).toInt();
- ZanoWalletBase(WalletInfo walletInfo, DerivationInfo derivationInfo, String password)
+ ZanoWalletBase(WalletInfo walletInfo, DerivationInfo derivationInfo, String password,
+ this._encryptionFileUtils)
: balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty(CryptoCurrency.zano)}),
_isTransactionUpdating = false,
_hasSyncAfterStartup = false,
@@ -126,74 +148,138 @@ abstract class ZanoWalletBase
@override
Future<void> changePassword(String password) async {
setPassword(password);
+ if (_hasSeed) {
+ await saveKeysFile(_password, _encryptionFileUtils);
+ saveKeysFile(_password, _encryptionFileUtils, true);
+ }
}
- static Future<ZanoWallet> create({required WalletCredentials credentials}) async {
+ static Future<ZanoWallet> create({
+ required WalletCredentials credentials,
+ required EncryptionFileUtils encryptionFileUtils,
+ }) async {
final wallet = ZanoWallet(credentials.walletInfo!,
- await credentials.walletInfo!.getDerivationInfo(), credentials.password!);
+ await credentials.walletInfo!.getDerivationInfo(), credentials.password!, encryptionFileUtils);
await wallet.initWallet();
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
- final createWalletResult = await wallet.createWallet(path, credentials.password!);
+ final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
+ final providedMnemonic =
+ credentials is ZanoNewWalletCredentials ? credentials.mnemonic : null;
+ final mnemonic = providedMnemonic ?? generateBip39Mnemonic(strength: strength);
+ final passphrase = credentials.passphrase ?? '';
+ final secretDerivation = getSecretDerivationFromBip39(mnemonic, passphrase: passphrase);
+ final creationTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
+ final createWalletResult = await wallet.restoreWalletFromDerivations(
+ path,
+ credentials.password!,
+ secretDerivation,
+ creationTimestamp: creationTimestamp,
+ );
await wallet.initWallet();
+ wallet.seed = mnemonic;
+ wallet.passphrase = passphrase;
await wallet.parseCreateWalletResult(createWalletResult);
- if (credentials.passphrase != null) {
- await wallet.setPassphrase(credentials.passphrase!);
- wallet.seed = await createWalletResult.seed(wallet);
- wallet.passphrase = await wallet.getPassphrase();
- }
+ await wallet._syncSecretsAndDerivation();
await wallet.init(createWalletResult.wi.address);
+ await wallet.save();
return wallet;
}
- static Future<ZanoWallet> restore(
- {required ZanoRestoreWalletFromSeedCredentials credentials}) async {
+ static Future<ZanoWallet> restore({
+ required ZanoRestoreWalletFromSeedCredentials credentials,
+ required EncryptionFileUtils encryptionFileUtils,
+ }) async {
final wallet = ZanoWallet(credentials.walletInfo!,
- await credentials.walletInfo!.getDerivationInfo(), credentials.password!);
+ await credentials.walletInfo!.getDerivationInfo(), credentials.password!, encryptionFileUtils);
await wallet.initWallet();
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
- final createWalletResult = await wallet.restoreWalletFromSeed(
- path, credentials.password!, credentials.mnemonic, credentials.passphrase);
+ final passphrase = credentials.passphrase ?? '';
+ final CreateWalletResult createWalletResult;
+ final isBip39 = isBip39Seed(credentials.mnemonic);
+ final creationTimestamp = ZanoUtils.creationTimestampFromHeight(credentials.height ?? 0);
+ if (isBip39) {
+ final secretDerivation =
+ getSecretDerivationFromBip39(credentials.mnemonic, passphrase: passphrase);
+ createWalletResult = await wallet.restoreWalletFromDerivations(
+ path,
+ credentials.password!,
+ secretDerivation,
+ creationTimestamp: creationTimestamp,
+ );
+ } else {
+ createWalletResult = await wallet.restoreWalletFromSeed(
+ path,
+ credentials.password!,
+ credentials.mnemonic,
+ credentials.passphrase,
+ creationTimestamp: creationTimestamp,
+ );
+ }
await wallet.initWallet();
+ wallet.seed = credentials.mnemonic;
+ wallet.passphrase = passphrase;
await wallet.parseCreateWalletResult(createWalletResult);
- if (credentials.passphrase != null) {
- await wallet.setPassphrase(credentials.passphrase!);
- wallet.seed = await createWalletResult.seed(wallet);
- wallet.passphrase = await wallet.getPassphrase();
+ if (!isBip39) {
+ try {
+ if (passphrase.isNotEmpty) {
+ await wallet.setPassphrase(passphrase);
+ }
+ final nativeSeed = await createWalletResult.seed(wallet);
+ if (nativeSeed.isNotEmpty) {
+ wallet.seed = nativeSeed;
+ }
+ } catch (e) {
+ printV('native seed not available yet: $e');
+ }
}
+ await wallet._syncSecretsAndDerivation();
await wallet.init(createWalletResult.wi.address);
+ await wallet.save();
return wallet;
}
static Future<ZanoWallet> open(
- {required String name, required String password, required WalletInfo walletInfo}) async {
+ {required String name,
+ required String password,
+ required WalletInfo walletInfo,
+ required EncryptionFileUtils encryptionFileUtils}) async {
final path = await pathForWallet(name: name, type: walletInfo.type);
+ final wallet =
+ ZanoWallet(walletInfo, await walletInfo.getDerivationInfo(), password, encryptionFileUtils);
+
+ await wallet._loadKeysFileIfPresent();
+
+ late final CreateWalletResult createWalletResult;
if (ZanoWalletApi.openWalletCache[path] != null) {
- final wallet = ZanoWallet(walletInfo, await walletInfo.getDerivationInfo(), password);
- await wallet.parseCreateWalletResult(ZanoWalletApi.openWalletCache[path]!).then((_) {
- unawaited(wallet.init(ZanoWalletApi.openWalletCache[path]!.wi.address));
- });
- return wallet;
+ createWalletResult = ZanoWalletApi.openWalletCache[path]!;
} else {
- final wallet = ZanoWallet(walletInfo, await walletInfo.getDerivationInfo(), password);
await wallet.initWallet();
- final createWalletResult = await wallet.loadWallet(path, password);
- await wallet.parseCreateWalletResult(createWalletResult).then((_) {
- unawaited(wallet.init(createWalletResult.wi.address));
- });
- return wallet;
+ createWalletResult = await wallet.loadWallet(path, password);
}
+ await wallet.parseCreateWalletResult(createWalletResult);
+ if (!wallet._hasSeed) {
+ await wallet._loadSeedIfAvailable();
+ }
+ await wallet._syncSecretsAndDerivation();
+ unawaited(wallet.init(createWalletResult.wi.address));
+ return wallet;
}
Future<void> parseCreateWalletResult(CreateWalletResult result) async {
hWallet = result.walletId;
- seed = await result.seed(this);
keys = ZanoWalletKeys(
privateSpendKey: result.privateSpendKey,
privateViewKey: result.privateViewKey,
publicSpendKey: result.publicSpendKey,
publicViewKey: result.publicViewKey,
);
- passphrase = await getPassphrase();
+ if ((passphrase ?? '').isEmpty) {
+ try {
+ passphrase = await getPassphrase();
+ } catch (e) {
+ printV('passphrase not available yet: $e');
+ }
+ }
printV('setting hWallet = ${result.walletId}');
walletAddresses.address = result.wi.address;
@@ -214,6 +300,126 @@ abstract class ZanoWalletBase
}
}
+ bool _isWalletReady(GetWalletStatusResult walletStatus) =>
+ !walletStatus.isInLongRefresh && walletStatus.walletState == _walletStateReady;
+
+ Future<bool> _hasKeysFile() async {
+ try {
+ final path = await makeKeysFilePath();
+ return File(path).existsSync() || File('$path.backup').existsSync();
+ } catch (_) {
+ return false;
+ }
+ }
+
+ Future<void> _loadKeysFileIfPresent() async {
+ if (!await _hasKeysFile()) {
+ return;
+ }
+ final path = await makeKeysFilePath();
+ Future<WalletKeysData> read(String readPath) async {
+ final jsonSource = await _encryptionFileUtils.read(path: readPath, password: _password);
+ return WalletKeysData.fromJSON(json.decode(jsonSource) as Map<String, dynamic>);
+ }
+
+ try {
+ WalletKeysData keysData;
+ if (File(path).existsSync()) {
+ keysData = await read(path);
+ } else {
+ keysData = await read('$path.backup');
+ await saveKeysFile(_password, _encryptionFileUtils);
+ }
+ if (keysData.mnemonic != null && keysData.mnemonic!.trim().isNotEmpty) {
+ seed = keysData.mnemonic!;
+ }
+ if (keysData.passphrase != null && keysData.passphrase!.isNotEmpty) {
+ passphrase = keysData.passphrase;
+ }
+ } catch (e) {
+ printV('error reading zano keys file $e');
+ }
+ }
+
+ Future<void> _saveKeysFileIfNeeded() async {
+ if (!_hasSeed || await _hasKeysFile()) {
+ return;
+ }
+ await saveKeysFile(_password, _encryptionFileUtils);
+ saveKeysFile(_password, _encryptionFileUtils, true);
+ }
+
+ Future<void> _loadSeedIfAvailable() async {
+ if (!isBip39Seed(seed)) {
+ try {
+ final loaded = await getSeed();
+ if (loaded.isNotEmpty) {
+ seed = loaded;
+ }
+ } catch (e) {
+ printV('seed not available yet: $e');
+ }
+ }
+ if ((passphrase ?? '').isEmpty) {
+ try {
+ passphrase = await getPassphrase() ?? passphrase;
+ } catch (e) {
+ printV('passphrase not available yet: $e');
+ }
+ }
+ }
+
+ Future<void> _syncSecretsAndDerivation() async {
+ if (!_hasSeed || _didSyncSecrets) {
+ return;
+ }
+ try {
+ await _persistSecretsIfNeeded();
+ await _setDerivationForSeed();
+ await _saveKeysFileIfNeeded();
+ _didSyncSecrets = true;
+ } catch (e) {
+ printV('error persisting zano secrets $e');
+ }
+ }
+
+ Future<void> _persistSecretsIfNeeded() async {
+ if (isBip39Seed(seed) && await getBip39Mnemonic() == null) {
+ await setBip39Secrets(
+ mnemonic: seed,
+ creationTimestamp: walletInfo.restoreHeight > 0
+ ? ZanoUtils.creationTimestampFromHeight(walletInfo.restoreHeight)
+ : DateTime.now().millisecondsSinceEpoch ~/ 1000,
+ );
+ }
+ if ((passphrase ?? '').isNotEmpty && await getPassphrase() == null) {
+ await setPassphrase(passphrase!);
+ }
+ }
+
+ Future<void> _setDerivationForSeed() async {
+ if (!_hasSeed) {
+ return;
+ }
+ final di = await walletInfo.getDerivationInfo();
+ if (isBip39Seed(seed)) {
+ if (di.derivationType == DerivationType.bip39 &&
+ (di.derivationPath ?? '').isNotEmpty) {
+ return;
+ }
+ di.derivationType = DerivationType.bip39;
+ di.derivationPath = "m/44'/128'/0'/0/0";
+ await di.save();
+ return;
+ }
+ if (di.derivationType != DerivationType.bip39 && di.derivationPath == 'legacy') {
+ return;
+ }
+ di.derivationType = DerivationType.unknown;
+ di.derivationPath = 'legacy';
+ await di.save();
+ }
+
@override
Future<void> close({bool shouldCleanup = true}) async {
closeWallet(null);
@@ -310,12 +516,13 @@ abstract class ZanoWalletBase
Future<Map<String, ZanoTransactionInfo>> fetchTransactions() async {
try {
final transfers = <Transfer>[];
+ var offset = 0;
late GetRecentTxsAndInfoResult result;
do {
- result = await getRecentTxsAndInfo(offset: 0, count: _txChunkSize);
- // _lastTxIndex += result.transfers.length;
+ result = await getRecentTxsAndInfo(offset: offset, count: _txChunkSize);
transfers.addAll(result.transfers);
- } while (result.lastItemIndex + 1 < result.totalTransfers);
+ offset = result.lastItemIndex + 1;
+ } while (offset < result.totalTransfers);
return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
} catch (e) {
printV((e.toString()));
@@ -334,12 +541,18 @@ abstract class ZanoWalletBase
@override
Future<void> renameWalletFiles(String newWalletName) async {
- final currentWalletPath = await pathForWallet(name: name, type: type);
+ final currentWalletDirPath = await pathForWalletDir(name: name, type: type);
+ final currentWalletPath = '$currentWalletDirPath/$name';
final currentCacheFile = File(currentWalletPath);
final currentKeysFile = File('$currentWalletPath.keys');
final currentAddressListFile = File('$currentWalletPath.address.txt');
+ final currentPendingKiFile = File('$currentWalletPath.outkey2ki');
+ final currentSecretsFile = File(p.join(currentWalletDirPath, 'zano-secrets.json.bin'));
+ final currentCakeKeysFile = File('$currentWalletPath$_cakeKeysFileSuffix');
+ final currentCakeKeysBackupFile = File('$currentWalletPath$_cakeKeysFileSuffix.backup');
- final newWalletPath = await pathForWallet(name: newWalletName, type: type);
+ final newWalletDirPath = await pathForWalletDir(name: newWalletName, type: type);
+ final newWalletPath = '$newWalletDirPath/$newWalletName';
// Copies current wallet files into new wallet name's dir and files
if (currentCacheFile.existsSync()) {
@@ -351,9 +564,22 @@ abstract class ZanoWalletBase
if (currentAddressListFile.existsSync()) {
await currentAddressListFile.copy('$newWalletPath.address.txt');
}
+ if (currentPendingKiFile.existsSync()) {
+ await currentPendingKiFile.copy('$newWalletPath.outkey2ki');
+ }
+ if (currentSecretsFile.existsSync()) {
+ await currentSecretsFile.copy(p.join(newWalletDirPath, 'zano-secrets.json.bin'));
+ }
+ if (currentCakeKeysFile.existsSync()) {
+ await currentCakeKeysFile.copy('$newWalletPath$_cakeKeysFileSuffix');
+ }
+ if (currentCakeKeysBackupFile.existsSync()) {
+ await currentCakeKeysBackupFile.copy('$newWalletPath$_cakeKeysFileSuffix.backup');
+ }
- // Delete old name's dir and files
- await Directory(currentWalletPath).delete(recursive: true);
+ // Delete the old wallet *directory*. pathForWallet() is the wallet file
+ // (.../<name>/<name>); Directory(filePath).delete leaves seed sidecars.
+ await Directory(currentWalletDirPath).delete(recursive: true);
}
@override
@@ -362,6 +588,7 @@ abstract class ZanoWalletBase
@override
Future<void> save() async {
try {
+ await _saveKeysFileIfNeeded();
await store();
await walletAddresses.updateAddressesInBox();
} catch (e) {
@@ -544,9 +771,12 @@ abstract class ZanoWalletBase
_updateSyncProgress(walletStatus);
// we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
- if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) {
+ if (_isWalletReady(walletStatus)) {
+ if (!_hasSeed) {
+ await _loadSeedIfAvailable();
+ }
+ await _syncSecretsAndDerivation();
final walletInfo = await getWalletInfo();
- seed = await walletInfo.wiExtended.seed(this);
keys = ZanoWalletKeys(
privateSpendKey: walletInfo.wiExtended.spendPrivateKey,
privateViewKey: walletInfo.wiExtended.viewPrivateKey,
diff --git a/cw_zano/lib/zano_wallet_api.dart b/cw_zano/lib/zano_wallet_api.dart
index cec26ed..7196c1d 100644
--- a/cw_zano/lib/zano_wallet_api.dart
+++ b/cw_zano/lib/zano_wallet_api.dart
@@ -46,12 +46,13 @@ mixin ZanoWalletApi {
void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
- void closeWallet(int? walletToClose, {bool force = false}) async {
+ Future<void> closeWallet(int? walletToClose, {bool force = false}) async {
printV('close_wallet ${walletToClose ?? hWallet}: $force');
if (Platform.isWindows || force) {
- final result = await _closeWallet(walletToClose ?? hWallet);
+ final id = walletToClose ?? hWallet;
+ final result = await _closeWallet(id);
printV('close_wallet result $result');
- openWalletCache.removeWhere((_, cwr) => cwr.walletId == (walletToClose ?? hWallet));
+ openWalletCache.removeWhere((_, cwr) => cwr.walletId == id);
}
}
@@ -76,31 +77,34 @@ mixin ZanoWalletApi {
Future<File> _getWalletSecretsFile() async {
final dir = await getWalletDir();
- final file = File(p.join(dir.path, "zano-secrets.json.bin"));
- return file;
+ return File(p.join(dir.path, 'zano-secrets.json.bin'));
}
Future<Map<String, dynamic>> _getSecrets() async {
- final file = await _getWalletSecretsFile();
- if (!file.existsSync()) {
+ try {
+ final file = await _getWalletSecretsFile();
+ if (!file.existsSync()) {
+ return {};
+ }
+ final data = file.readAsBytesSync();
+ final b64 = convert.base64.encode(data);
+ final respStr = await invokeMethod('decrypt_data', {'buff': b64});
+ final resp = convert.json.decode(respStr);
+ final dataBytes = convert.base64.decode(resp['result']['res_buff'] as String);
+ final dataStr = convert.utf8.decode(dataBytes);
+ return convert.json.decode(dataStr) as Map<String, dynamic>;
+ } catch (e) {
+ printV('error reading zano secrets $e');
return {};
}
- final data = file.readAsBytesSync();
- final b64 = convert.base64.encode(data);
- final respStr = await invokeMethod("decrypt_data", {"buff": "$b64"});
- final resp = convert.json.decode(respStr);
- final dataBytes = convert.base64.decode(resp["result"]["res_buff"] as String);
- final dataStr = convert.utf8.decode(dataBytes);
- final dataObject = convert.json.decode(dataStr);
- return dataObject as Map<String, dynamic>;
}
Future<void> _setSecrets(Map<String, dynamic> data) async {
final dataStr = convert.json.encode(data);
final b64 = convert.base64.encode(convert.utf8.encode(dataStr));
- final respStr = await invokeMethod("encrypt_data", {"buff": "$b64"});
+ final respStr = await invokeMethod('encrypt_data', {'buff': b64});
final resp = convert.json.decode(respStr);
- final dataBytes = convert.base64.decode(resp["result"]["res_buff"] as String);
+ final dataBytes = convert.base64.decode(resp['result']['res_buff'] as String);
final file = await _getWalletSecretsFile();
file.writeAsBytesSync(dataBytes);
}
@@ -124,7 +128,25 @@ mixin ZanoWalletApi {
return _setWalletSecret("passphrase", passphrase);
}
+ Future<String?> getBip39Mnemonic() async {
+ return await _getWalletSecret("bip39_mnemonic");
+ }
+
+ Future<void> setBip39Secrets({
+ required String mnemonic,
+ required int creationTimestamp,
+ }) async {
+ final secrets = await _getSecrets();
+ secrets["bip39_mnemonic"] = mnemonic;
+ secrets["creation_timestamp"] = creationTimestamp.toString();
+ await _setSecrets(secrets);
+ }
+
Future<String> getSeed() async {
+ final bip39Mnemonic = await getBip39Mnemonic();
+ if (bip39Mnemonic != null && bip39Mnemonic.isNotEmpty) {
+ return bip39Mnemonic;
+ }
final passphrase = await getPassphrase();
final respStr = await invokeMethod("get_restore_info", {"seed_password": passphrase ?? ""});
final resp = convert.json.decode(respStr);
@@ -328,10 +350,59 @@ mixin ZanoWalletApi {
return result;
}
+ Future<CreateWalletResult> restoreWalletFromDerivations(
+ String path,
+ String password,
+ String secretDerivation, {
+ bool isAuditable = false,
+ required int creationTimestamp,
+ }) async {
+ printV('restore_wallet_from_derivations path $path');
+ final params = jsonEncode({
+ "creation_timestamp": creationTimestamp,
+ "is_auditable": isAuditable,
+ "pass": password,
+ "path": path,
+ "secret_derivation": secretDerivation,
+ });
+ final json = await callSyncMethod('restore_from_derivations', 0, params);
+ final map = jsonDecode(json) as Map<String, dynamic>?;
+ if (map?['error'] != null) {
+ final code = map!['error']!['code'] ?? '';
+ final message = map['error']!['message'] ?? '';
+ if (code == Consts.errorWrongSeed) {
+ throw RestoreFromSeedsException(
+ '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.');
+ } else if (code == Consts.errorAlreadyExists) {
+ throw RestoreFromSeedsException('Error restoring wallet, already exists');
+ }
+ throw RestoreFromSeedsException('Error restoring wallet, $message ($code)');
+ }
+ if (map?['result'] == null) {
+ throw RestoreFromSeedsException('Error restoring wallet, empty response');
+ }
+ final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
+ openWalletCache[path] = result;
+ printV('restore_wallet_from_derivations ${result.name} ${result.wi.address}');
+ return result;
+ }
+
Future<CreateWalletResult> restoreWalletFromSeed(
- String path, String password, String seed, String? passphrase) async {
+ String path,
+ String password,
+ String seed,
+ String? passphrase, {
+ required int creationTimestamp,
+ }) async {
printV('restore_wallet path $path');
- final json = zano.PlainWallet_restore(seed, path, password, passphrase ?? '');
+ final params = jsonEncode({
+ "creation_timestamp": creationTimestamp,
+ "pass": password,
+ "path": path,
+ "seed_phrase": seed,
+ "seed_pass": passphrase ?? '',
+ });
+ final json = await callSyncMethod('restore', 0, params);
final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) {
final code = map!['error']!['code'] ?? '';
diff --git a/cw_zano/lib/zano_wallet_service.dart b/cw_zano/lib/zano_wallet_service.dart
index 9a33bc3..cf50509 100644
--- a/cw_zano/lib/zano_wallet_service.dart
+++ b/cw_zano/lib/zano_wallet_service.dart
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:collection/collection.dart';
+import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
@@ -14,8 +15,14 @@ import 'package:hive/hive.dart';
import 'package:monero/zano.dart' as zano;
class ZanoNewWalletCredentials extends WalletCredentials {
- ZanoNewWalletCredentials({required String name, String? password, required String? passphrase})
- : super(name: name, password: password, passphrase: passphrase);
+ ZanoNewWalletCredentials({
+ required String name,
+ String? password,
+ required String? passphrase,
+ this.mnemonic,
+ }) : super(name: name, password: password, passphrase: passphrase);
+
+ final String? mnemonic;
}
class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials {
@@ -52,7 +59,9 @@ class ZanoWalletService extends WalletService<
ZanoRestoreWalletFromSeedCredentials,
ZanoRestoreWalletFromKeysCredentials,
ZanoNewWalletCredentials> {
- ZanoWalletService();
+ ZanoWalletService(this.isDirect);
+
+ final bool isDirect;
static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync();
@@ -65,7 +74,8 @@ class ZanoWalletService extends WalletService<
@override
Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
printV('zanowallet service create isTestnet $isTestnet');
- return await ZanoWalletBase.create(credentials: credentials);
+ return await ZanoWalletBase.create(
+ credentials: credentials, encryptionFileUtils: encryptionFileUtilsFor(isDirect));
}
@override
@@ -82,12 +92,21 @@ class ZanoWalletService extends WalletService<
}
try {
final wallet =
- await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo);
+ await ZanoWalletBase.open(
+ name: name,
+ password: password,
+ walletInfo: walletInfo,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect));
saveBackup(name);
return wallet;
} catch (e) {
+ printV('openWallet $name failed: $e');
await restoreWalletFilesFromBackup(name);
- return await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo);
+ return await ZanoWalletBase.open(
+ name: name,
+ password: password,
+ walletInfo: walletInfo,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect));
}
}
@@ -115,15 +134,24 @@ class ZanoWalletService extends WalletService<
throw Exception('Wallet not found');
}
final currentWallet =
- ZanoWallet(currentWalletInfo, await currentWalletInfo.getDerivationInfo(), password);
+ ZanoWallet(currentWalletInfo, await currentWalletInfo.getDerivationInfo(), password,
+ encryptionFileUtilsFor(isDirect));
+
+ final oldPath = await pathForWallet(name: currentName, type: getType());
+ final cached = ZanoWalletApi.openWalletCache.remove(oldPath);
+ if (cached != null) {
+ currentWallet.hWallet = cached.walletId;
+ await currentWallet.closeWallet(cached.walletId, force: true);
+ }
await currentWallet.renameWalletFiles(newName);
- final newWalletInfo = currentWalletInfo;
- newWalletInfo.id = WalletBase.idFor(newName, getType());
- newWalletInfo.name = newName;
-
- await newWalletInfo.save();
+ final newDirPath = await pathForWalletDir(name: newName, type: getType());
+ currentWalletInfo.id = WalletBase.idFor(newName, getType());
+ currentWalletInfo.name = newName;
+ currentWalletInfo.dirPath = newDirPath;
+ currentWalletInfo.path = '$newDirPath/$newName';
+ await currentWalletInfo.save();
}
@override
@@ -135,7 +163,8 @@ class ZanoWalletService extends WalletService<
@override
Future<ZanoWallet> restoreFromSeed(ZanoRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
- return ZanoWalletBase.restore(credentials: credentials);
+ return ZanoWalletBase.restore(
+ credentials: credentials, encryptionFileUtils: encryptionFileUtilsFor(isDirect));
}
@override
diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml
index 7e8ede8..6371134 100644
--- a/cw_zano/pubspec.yaml
+++ b/cw_zano/pubspec.yaml
@@ -11,6 +11,8 @@ environment:
dependencies:
flutter:
sdk: flutter
+ bip39: ^1.0.6
+ bip32: ^2.0.0
ffi: ^2.0.1
http: ^1.1.0
path_provider: ^2.0.11
@@ -25,7 +27,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c.git
- ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
+ ref: cd4fe366cc8d3c188c91de279f43b29c0e044b15
path: impls/monero.dart
dev_dependencies:
flutter_test:
diff --git a/cw_zano/test/bip39_seed_test.dart b/cw_zano/test/bip39_seed_test.dart
new file mode 100644
index 0000000..26cda4c
--- /dev/null
+++ b/cw_zano/test/bip39_seed_test.dart
@@ -0,0 +1,38 @@
+import 'package:cw_zano/bip39_seed.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ group("Zano bip39 secret derivation", () {
+ group("Test Wallet 1", () {
+ final bip39Seed =
+ 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
+ final expectedSecretDerivation =
+ "bfafd1eb0e43da200c5c11537d355e458e7c326b3bc1b19f4546573d6bac9d0f";
+ final expectedSecretDerivationWithPassphrase =
+ "674ba1caeaf8a79e66a026858c78d4e565032953e98d39bdea12559bcae86a0c";
+
+ test("Get secret derivation from bip39", () {
+ final secretDerivation = getSecretDerivationFromBip39(bip39Seed);
+ expect(secretDerivation, expectedSecretDerivation);
+ });
+
+ test("Get secret derivation from bip39 with passphrase", () {
+ final secretDerivation =
+ getSecretDerivationFromBip39(bip39Seed, passphrase: 'TREZOR');
+ expect(secretDerivation, expectedSecretDerivationWithPassphrase);
+ });
+ });
+
+ group("Test Wallet 2", () {
+ final bip39Seed =
+ 'melt veteran patrol echo miss fat wrap apple crowd eyebrow weird boring';
+ final expectedSecretDerivation =
+ "8b2693e1d60b0431e3208762bc8a3f232890f50a3fea1e9605ec10a86afe1800";
+
+ test("Get secret derivation from bip39", () {
+ final secretDerivation = getSecretDerivationFromBip39(bip39Seed);
+ expect(secretDerivation, expectedSecretDerivation);
+ });
+ });
+ });
+}
diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart
index d627c1a..3700a1c 100644
--- a/lib/core/wallet_creation_service.dart
+++ b/lib/core/wallet_creation_service.dart
@@ -86,6 +86,7 @@ class WalletCreationService {
case WalletType.dogecoin:
case WalletType.nano:
case WalletType.zcash:
+ case WalletType.zano:
case WalletType.decred:
return true;
case WalletType.monero:
@@ -93,7 +94,6 @@ class WalletCreationService {
case WalletType.none:
case WalletType.haven:
case WalletType.banano:
- case WalletType.zano:
return false;
}
}
diff --git a/lib/di.dart b/lib/di.dart
index 147f190..ee904e2 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -1341,7 +1341,7 @@ Future<void> setup({
case WalletType.wownero:
return wownero!.createWowneroWalletService(_unspentCoinsInfoSource);
case WalletType.zano:
- return zano!.createZanoWalletService();
+ return zano!.createZanoWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.decred:
return decred!.createDecredWalletService(
_unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput);
diff --git a/lib/reactions/wallet_utils.dart b/lib/reactions/wallet_utils.dart
index 56bf208..57d11a4 100644
--- a/lib/reactions/wallet_utils.dart
+++ b/lib/reactions/wallet_utils.dart
@@ -17,11 +17,11 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.monero:
case WalletType.dogecoin:
case WalletType.zcash:
+ case WalletType.zano:
case WalletType.decred:
return true;
case WalletType.wownero:
case WalletType.haven:
- case WalletType.zano:
case WalletType.none:
return false;
}
diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart
index d1b5cf0..933a5fe 100644
--- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart
+++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart
@@ -286,7 +286,12 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
'wallet_restore_from_seed_blockheight_textfield_key',
),
onHeightOrDateEntered: widget.onHeightOrDateEntered,
- hasDatePicker: [WalletType.monero, WalletType.wownero, WalletType.zcash].contains(
+ hasDatePicker: [
+ WalletType.monero,
+ WalletType.wownero,
+ WalletType.zcash,
+ WalletType.zano,
+ ].contains(
widget.type,
),
walletType: widget.type,
diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart
index 1a0db8d..af8e9b9 100644
--- a/lib/src/screens/restore/wallet_restore_page.dart
+++ b/lib/src/screens/restore/wallet_restore_page.dart
@@ -13,6 +13,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
+import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -606,7 +607,13 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
final seedWords = seedPhrase.split(' ');
if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true;
- if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true;
+ if (walletRestoreViewModel.type == WalletType.zano) {
+ if (seedWords.length == 26) return true;
+ if ([12, 24].contains(seedWords.length) && zano!.isBip39Seed(seedPhrase.trim())) {
+ return true;
+ }
+ return false;
+ }
if (seedWords.length == 12 && walletRestoreViewModel.type == WalletType.monero) {
return walletRestoreFromSeedFormKey.currentState?.blockchainHeightKey.currentState
diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart
index 273db82..68160c2 100644
--- a/lib/src/widgets/blockchain_height_widget.dart
+++ b/lib/src/widgets/blockchain_height_widget.dart
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/utils/date_picker.dart';
import 'package:cake_wallet/wownero/wownero.dart';
+import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -200,6 +201,8 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
height = wownero!.getHeightByDate(date: date);
} else if (widget.walletType == WalletType.zcash) {
height = await zcash!.getHeightByDate(date);
+ } else if (widget.walletType == WalletType.zano) {
+ height = zano!.getHeightByDate(date: date);
} else {
throw Exception("unknown currency in BlockchainHeightWidget");
}
diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart
index 39e9346..f8f1ce1 100644
--- a/lib/view_model/advanced_privacy_settings_view_model.dart
+++ b/lib/view_model/advanced_privacy_settings_view_model.dart
@@ -59,6 +59,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.solana:
case WalletType.tron:
case WalletType.zcash:
+ case WalletType.zano:
case WalletType.decred:
return true;
@@ -74,7 +75,6 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.wownero:
case WalletType.none:
case WalletType.haven:
- case WalletType.zano:
return false;
}
}
diff --git a/lib/view_model/wallet_groups_display_view_model.dart b/lib/view_model/wallet_groups_display_view_model.dart
index fd8fdc2..b394da5 100644
--- a/lib/view_model/wallet_groups_display_view_model.dart
+++ b/lib/view_model/wallet_groups_display_view_model.dart
@@ -128,8 +128,9 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
bool isNonSeedWallet = wallet.isNonSeedWallet;
- bool isNotMoneroBip39Wallet =
- wallet.type == WalletType.monero && di.derivationType != DerivationType.bip39;
+ bool isNotBip39CoinWallet =
+ (wallet.type == WalletType.monero || wallet.type == WalletType.zano) &&
+ di.derivationType != DerivationType.bip39;
bool isNotDecredBip39Wallet = wallet.type == WalletType.decred &&
di.derivationType != DerivationType.bip39 &&
@@ -142,7 +143,7 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
isElectrumDerivationType ||
isSameTypeAsSelectedWallet ||
isNonSeedWallet ||
- isNotMoneroBip39Wallet ||
+ isNotBip39CoinWallet ||
isNotDecredBip39Wallet;
}
diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart
index e313ca0..6597776 100644
--- a/lib/view_model/wallet_new_vm.dart
+++ b/lib/view_model/wallet_new_vm.dart
@@ -139,6 +139,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
name: name,
password: walletPassword,
passphrase: passphrase,
+ mnemonic: newWalletArguments!.mnemonic,
);
case WalletType.zcash:
return zcash!.createZcashNewWalletCredentials(
diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart
index 16767a6..350b213 100644
--- a/lib/view_model/wallet_restore_view_model.dart
+++ b/lib/view_model/wallet_restore_view_model.dart
@@ -83,8 +83,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
late final bool hasSeedLanguageSelector =
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
- late final bool hasBlockchainHeightSelector =
- [WalletType.monero, WalletType.haven, WalletType.wownero, WalletType.zcash].contains(type);
+ late final bool hasBlockchainHeightSelector = [
+ WalletType.monero,
+ WalletType.haven,
+ WalletType.wownero,
+ WalletType.zcash,
+ WalletType.zano,
+ ].contains(type);
late final bool hasRestoreFromPrivateKey = [
WalletType.ethereum,
@@ -204,13 +209,15 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
height: height,
);
case WalletType.zano:
- return zano!.createZanoRestoreWalletFromSeedCredentials(
+ final credentials = zano!.createZanoRestoreWalletFromSeedCredentials(
name: name,
password: password,
height: height,
passphrase: passphrase ?? '',
mnemonic: seed,
);
+ credentials.derivationInfo = derivationInfo;
+ return credentials;
case WalletType.decred:
return decred!.createDecredRestoreWalletFromSeedCredentials(
name: name,
@@ -363,6 +370,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
seedKey: seedKey,
node: node,
);
+ case WalletType.zano:
+ final mnemonic = (credentials['seed'] as String?)?.trim() ?? '';
+ if (mnemonic.isEmpty) break;
+ if (zano!.isBip39Seed(mnemonic)) {
+ return [
+ DerivationInfo(derivationType: DerivationType.bip39),
+ ];
+ }
+ return [
+ DerivationInfo(derivationType: DerivationType.unknown),
+ ];
default:
break;
}
diff --git a/lib/zano/cw_zano.dart b/lib/zano/cw_zano.dart
index ce2f82c..ab14e33 100644
--- a/lib/zano/cw_zano.dart
+++ b/lib/zano/cw_zano.dart
@@ -46,7 +46,7 @@ class CWZano extends Zano {
@override
List<String> getWordList(String language) {
assert(language.toLowerCase() == LanguageList.english.toLowerCase());
- return EnglishMnemonics.words;
+ return [...zano_bip39.bip39EnglishWords, ...EnglishMnemonics.words];
}
@override
@@ -61,9 +61,18 @@ class CWZano extends Zano {
}
@override
- WalletCredentials createZanoNewWalletCredentials(
- {required String name, required String? password, required String? passphrase}) {
- return ZanoNewWalletCredentials(name: name, password: password, passphrase: passphrase);
+ WalletCredentials createZanoNewWalletCredentials({
+ required String name,
+ required String? password,
+ required String? passphrase,
+ String? mnemonic,
+ }) {
+ return ZanoNewWalletCredentials(
+ name: name,
+ password: password,
+ passphrase: passphrase,
+ mnemonic: mnemonic,
+ );
}
@override
@@ -122,8 +131,8 @@ class CWZano extends Zano {
// }
@override
- WalletService createZanoWalletService() {
- return ZanoWalletService();
+ WalletService createZanoWalletService(bool isDirect) {
+ return ZanoWalletService(isDirect);
}
@override
@@ -146,6 +155,12 @@ class CWZano extends Zano {
@override
bool validateAddress(String address) => ZanoUtils.validateAddress(address);
+ @override
+ bool isBip39Seed(String mnemonic) => zano_bip39.isBip39Seed(mnemonic);
+
+ @override
+ int getHeightByDate({required DateTime date}) => ZanoUtils.heightByDate(date);
+
@override
Map<String, List<int>> debugCallLength() {
return api.debugCallLength();
diff --git a/pubspec.lock b/pubspec.lock
index cbcdbf5..90493ac 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -2050,8 +2050,8 @@ packages:
dependency: "direct overridden"
description:
path: "impls/monero.dart"
- ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
- resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ ref: cd4fe366cc8d3c188c91de279f43b29c0e044b15
+ resolved-ref: cd4fe366cc8d3c188c91de279f43b29c0e044b15
url: "https://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml
index b91e7dc..90cceec 100644
--- a/pubspec_overrides.yaml
+++ b/pubspec_overrides.yaml
@@ -960,7 +960,7 @@ dependency_overrides:
monero:
git:
url: https://github.com/mrcyjanek/monero_c.git
- ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
+ ref: cd4fe366cc8d3c188c91de279f43b29c0e044b15
path: ./impls/monero.dart
msgpack_dart:
git:
diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh
index 8d0d6e5..f4434ab 100755
--- a/scripts/prepare_moneroc.sh
+++ b/scripts/prepare_moneroc.sh
@@ -16,7 +16,7 @@ fi
# NOTE: Make sure to update monero_c prebuilds link in workflow files
# https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
git fetch -a
-git checkout 5952bef2ec01b0b7e57c11613cbdc081dcd727c5
+git checkout cd4fe366cc8d3c188c91de279f43b29c0e044b15
git reset --hard
git submodule update --init --force --recursive
diff --git a/tool/configure.dart b/tool/configure.dart
index a741c20..2f17046 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -1178,6 +1178,7 @@ import 'package:cw_core/zano_asset.dart';
import 'package:hive/hive.dart';
""";
const zanoCWHeaders = """
+import 'package:cw_zano/bip39_seed.dart' as zano_bip39;
import 'package:cw_zano/mnemonics/english.dart';
import 'package:cw_zano/model/zano_transaction_credentials.dart';
import 'package:cw_zano/model/zano_transaction_info.dart';
@@ -1196,12 +1197,12 @@ abstract class Zano {
List<String> getWordList(String language);
WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
- WalletCredentials createZanoNewWalletCredentials({required String name, required String? password, required String? passphrase});
+ WalletCredentials createZanoNewWalletCredentials({required String name, required String? password, required String? passphrase, String? mnemonic});
Map<String, String> getKeys(Object wallet);
Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency});
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
int formatterParseAmount({required String amount, required CryptoCurrency currency});
- WalletService createZanoWalletService();
+ WalletService createZanoWalletService(bool isDirect);
CryptoCurrency? assetOfTransaction(WalletBase wallet, TransactionInfo tx);
List<ZanoAsset> getZanoAssets(WalletBase wallet);
String getZanoAssetAddress(CryptoCurrency asset);
@@ -1211,6 +1212,8 @@ abstract class Zano {
Future<CryptoCurrency?> getZanoAsset(WalletBase wallet, String contractAddress);
String getAddress(WalletBase wallet);
bool validateAddress(String address);
+ bool isBip39Seed(String mnemonic);
+ int getHeightByDate({required DateTime date});
Map<String, List<int>> debugCallLength();
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
}
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.