fix: wownero P0s, fail-closed RNG, iOS scheme, dep pins

- resetToDefault omitted Wownero: the reset button null-crashed and left
  WOW wallets with an empty node list
- accountLabel was missing a return and rendered blank for WOW wallets
- sec_random now throws instead of returning empty bytes when the native
  entropy channel fails or under-delivers
- iOS builds no longer register the leftover cakewallet:// URL scheme
- WOW fee estimate now fetched from the connected daemon's
  get_fee_estimate RPC and cached; the inherited XMR constant table
  remains only as a pre-fetch fallback
- pin cake_backup, flutter_local_authentication, and bech32 git deps to
  their locked SHAs for reproducible builds
This commit is contained in:
jwinterm
2026-08-15 16:28:39 -04:00
parent 94bf0e8c41
commit 08fb925cea
7 changed files with 77 additions and 15 deletions
+9 -3
View File
@@ -14,8 +14,14 @@ Future<Uint8List> secRandom(int count) async {
final rng = Random.secure();
return Uint8List.fromList(List<int>.generate(count, (_) => rng.nextInt(byteSize)));
}
return await utils.invokeMethod<Uint8List>('sec_random', {'count': count}) ?? Uint8List.fromList([]);
} on PlatformException catch (_) {
return Uint8List.fromList([]);
final bytes = await utils.invokeMethod<Uint8List>('sec_random', {'count': count});
if (bytes == null || bytes.length != count) {
throw StateError('sec_random returned ${bytes?.length ?? 0} bytes, expected $count');
}
return bytes;
} on PlatformException catch (e) {
// Never fail open: an entropy source that cannot deliver must throw,
// not hand back an empty buffer for key material.
throw StateError('sec_random platform call failed: ${e.message}');
}
}
+61 -2
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
@@ -223,12 +224,38 @@ abstract class WowneroWalletBase
wownero_wallet.setTrustedDaemon(node.trusted);
syncStatus = ConnectedSyncStatus();
unawaited(_updateBaseFeeEstimate(node));
} catch (e) {
syncStatus = FailedSyncStatus();
printV(e);
}
}
Future<void> _updateBaseFeeEstimate(Node node) async {
try {
final rpcUri = node.isSSL
? Uri.https(node.uri.authority, '/json_rpc')
: Uri.http(node.uri.authority, '/json_rpc');
final client = ProxyWrapper().getHttpIOClient();
final response = await client
.post(rpcUri,
headers: {'Content-Type': 'application/json'},
body: json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_fee_estimate'}))
.timeout(const Duration(seconds: 15));
if (response.statusCode != 200) return;
final result =
(json.decode(response.body) as Map<String, dynamic>)['result'] as Map<String, dynamic>?;
final feePerByte = result?['fee'] as int?;
if (feePerByte != null && feePerByte > 0) {
_cachedBaseFeePerByte = feePerByte;
}
} catch (e) {
printV('get_fee_estimate failed, keeping fallback fee table: $e');
}
}
@override
Future<void> startSync() async {
try {
@@ -352,11 +379,26 @@ abstract class WowneroWalletBase
return PendingWowneroTransaction(pendingTransactionDescription);
}
// Base fee per byte in atomic units, fetched from the connected daemon's
// get_fee_estimate RPC. Null until the first successful fetch.
int? _cachedBaseFeePerByte;
// Typical 2-in/2-out ring-22 transaction weight. Only used for the
// pre-confirmation estimate; the real fee comes from wallet2 when the
// transaction is built.
static const _estimatedTxWeightBytes = 2300;
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
// FIXME: hardcoded value;
if (priority is MoneroTransactionPriority) {
final baseFeePerByte = _cachedBaseFeePerByte;
if (baseFeePerByte != null) {
return baseFeePerByte * _estimatedTxWeightBytes * _feeMultiplier(priority);
}
// Fallback while no daemon estimate is available yet. Inherited from
// upstream and calibrated for XMR, not WOW — kept only so the send
// screen shows something before get_fee_estimate returns.
switch (priority) {
case MoneroTransactionPriority.slow:
return 24590000;
@@ -374,6 +416,23 @@ abstract class WowneroWalletBase
return 0;
}
int _feeMultiplier(MoneroTransactionPriority priority) {
switch (priority) {
case MoneroTransactionPriority.slow:
return 1;
case MoneroTransactionPriority.automatic:
return 5;
case MoneroTransactionPriority.medium:
return 10;
case MoneroTransactionPriority.fast:
return 25;
case MoneroTransactionPriority.fastest:
return 1000;
default:
return 5;
}
}
@override
Future<void> save() async {
await walletAddresses.updateUsedSubaddress();
-4
View File
@@ -38,10 +38,6 @@
<string>hashbags</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
+3 -1
View File
@@ -82,6 +82,7 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
}
Future<void> resetToDefault(Box<Node> nodeSource) async {
final wowneroNodes = await loadDefaultNodes(WalletType.wownero);
final moneroNodes = await loadDefaultNodes(WalletType.monero);
final bitcoinElectrumServerList = await loadDefaultNodes(WalletType.bitcoin);
final litecoinElectrumServerList = await loadDefaultNodes(WalletType.litecoin);
@@ -94,7 +95,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum);
final bscNodes = await loadDefaultNodes(WalletType.bsc);
final nodes = moneroNodes +
final nodes = wowneroNodes +
moneroNodes +
bitcoinElectrumServerList +
litecoinElectrumServerList +
ethereumNodes +
@@ -389,7 +389,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
case WalletType.monero:
return monero!.getCurrentAccount(wallet).label;
case WalletType.wownero:
wownero!.getCurrentAccount(wallet).label;
return wownero!.getCurrentAccount(wallet).label;
default:
return '';
}
+3 -1
View File
@@ -38,6 +38,7 @@ dependencies:
flutter_local_authentication:
git:
url: https://github.com/cake-tech/flutter_local_authentication
ref: 75c8681ebef62398a87c8ae0cccb7cfddffa9734
package_info_plus: ^8.0.1
devicelocale: 0.8.1
auto_size_text: ^3.0.0
@@ -82,7 +83,7 @@ dependencies:
cake_backup:
git:
url: https://github.com/cake-tech/cake_backup.git
ref: main
ref: 3aba867dcab6737f6707782f5db15d71f303db38
version: 1.0.0
flutter_plugin_android_lifecycle: 2.0.23
path_provider_android: ^2.2.1
@@ -190,6 +191,7 @@ dependency_overrides:
bech32:
git:
url: https://github.com/cake-tech/bech32.git
ref: 05755063b593aa6cca0a4820a318e0ce17de6192
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
-3
View File
@@ -18,9 +18,6 @@ cp -rf ./ios/Runner/InfoBase.plist ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${APP_IOS_VERSION}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${APP_IOS_BUILD_NUMBER}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLName string ${APP_IOS_TYPE}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLSchemes array" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLSchemes: string ${APP_IOS_TYPE}" ./ios/Runner/Info.plist
universal_sed "s/PRODUCT_BUNDLE_IDENTIFIER = .*;/PRODUCT_BUNDLE_IDENTIFIER = $APP_IOS_BUNDLE_ID;/g" ./ios/Runner.xcodeproj/project.pbxproj
CONFIG_ARGS=""