Skip to content

Commit

Permalink
WIP: CW-665 Implement AirGapped Monero Transactions (#1535)
Browse files Browse the repository at this point in the history
* replace qr scanner with fast_scanner

* bump java version

* fix qr code scanning

* add flashlight and camera switch

* airgap work

* commitTransactionUR

* bump fast_scanner, fix configure script

* add option to restore wallet from NERO qr format

* fixes to the flow and logic
use gsed or otherwise it fails?

* remove Expanded() to fix URQR on release builds

* cache key to allow app updates

* rename cache key

* [skip ci] cache key.jks in cache_dependencies

* update fast_scanner to work on ios, with light mlkit dependency

* ui fixes

* error handling fix

* update fast_scanner to drop iOS dependency

* changes from review

* Update lib/entities/qr_scanner.dart

* changes from review

* remove useless commit

* don't set state multiple times
remove return Container() for non monero wallets

* return on fail
don't handle empty qr codes

* set node as trusted
display primary address in seed screen

* fix wow and haven

* migrate node to trusted

* - update trusted node for existing users
- update locales
- fix conflicts
- move menu item

---------

Co-authored-by: Omar Hatem <[email protected]>
  • Loading branch information
MrCyjaneK and OmarHatem28 authored Nov 9, 2024
1 parent 8e12fb1 commit cea3084
Show file tree
Hide file tree
Showing 84 changed files with 1,318 additions and 141 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/cache_dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@ jobs:
/opt/android/cake_wallet/cw_haven/android/.cxx
/opt/android/cake_wallet/scripts/monero_c/release
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}

- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
name: Generate Externals
run: |
cd /opt/android/cake_wallet/scripts/android/
source ./app_env.sh cakewallet
./build_monero_all.sh
./build_monero_all.sh
4 changes: 3 additions & 1 deletion .github/workflows/pr_test_build_android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ jobs:
path: |
/opt/android/cake_wallet/cw_haven/android/.cxx
/opt/android/cake_wallet/scripts/monero_c/release
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
/opt/android/cake_wallet/android/app/key.jks
key: android_${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}

- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
name: Generate Externals
Expand Down Expand Up @@ -116,6 +117,7 @@ jobs:
./build_mwebd.sh --dont-install
- name: Generate KeyStore
if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
run: |
cd /opt/android/cake_wallet/android/app
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS
Expand Down
5 changes: 5 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,8 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
configurations {
implementation.exclude module:'proto-google-common-protos'
implementation.exclude module:'protolite-well-known-types'
implementation.exclude module:'protobuf-javalite'
}
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx3072M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
1 change: 1 addition & 0 deletions assets/node_list.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-
uri: xmr-node.cakewallet.com:18081
is_default: true
trusted: true
-
uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
is_default: false
Expand Down
5 changes: 5 additions & 0 deletions cw_bitcoin/lib/pending_bitcoin_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,9 @@ class PendingBitcoinTransaction with PendingTransaction {
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
outputAddresses: outputAddresses,
fee: fee);

@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}
4 changes: 4 additions & 0 deletions cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ class PendingBitcoinCashTransaction with PendingTransaction {
fee: fee,
isReplaced: false,
);
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}
4 changes: 3 additions & 1 deletion cw_core/lib/monero_wallet_keys.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
class MoneroWalletKeys {
const MoneroWalletKeys(
{required this.privateSpendKey,
{required this.primaryAddress,
required this.privateSpendKey,
required this.privateViewKey,
required this.publicSpendKey,
required this.publicViewKey});

final String primaryAddress;
final String publicViewKey;
final String privateViewKey;
final String publicSpendKey;
Expand Down
3 changes: 3 additions & 0 deletions cw_core/lib/pending_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ mixin PendingTransaction {
int? get outputCount => null;
PendingChange? change;

bool shouldCommitUR() => false;

Future<void> commit();
Future<String?> commitUR();
}
5 changes: 5 additions & 0 deletions cw_evm/lib/pending_evm_chain_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ class PendingEVMChainTransaction with PendingTransaction {

return '0x${Hex.HEX.encode(txid)}';
}

@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ class WalletRestoreFromKeysException implements Exception {
WalletRestoreFromKeysException({required this.message});

final String message;

@override
String toString() {
return message;
}
}
1 change: 1 addition & 0 deletions cw_haven/lib/haven_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ abstract class HavenWalletBase

@override
MoneroWalletKeys get keys => MoneroWalletKeys(
primaryAddress: haven_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: haven_wallet.getSecretSpendKey(),
privateViewKey: haven_wallet.getSecretViewKey(),
publicSpendKey: haven_wallet.getPublicSpendKey(),
Expand Down
5 changes: 5 additions & 0 deletions cw_haven/lib/pending_haven_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ class PendingHavenTransaction with PendingTransaction {
rethrow;
}
}

@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}
1 change: 1 addition & 0 deletions cw_monero/lib/api/account_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:cw_monero/api/wallet.dart';
import 'package:monero/monero.dart' as monero;

monero.wallet? wptr = null;
bool get isViewOnly => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;

int _wlptrForW = 0;
monero.WalletListener? _wlptr = null;
Expand Down
24 changes: 18 additions & 6 deletions cw_monero/lib/api/transaction_history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import 'package:mutex/mutex.dart';


String getTxKey(String txId) {
return monero.Wallet_getTxKey(wptr!, txid: txId);
final txKey = monero.Wallet_getTxKey(wptr!, txid: txId);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final error = monero.Wallet_errorString(wptr!);
return txId+"_"+error;
}
return txKey;
}
final txHistoryMutex = Mutex();
monero.TransactionHistory? txhistory;
Expand Down Expand Up @@ -178,12 +184,13 @@ PendingTransactionDescription createTransactionMultDestSync(
);
}

void commitTransactionFromPointerAddress({required int address}) =>
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address));
String? commitTransactionFromPointerAddress({required int address, required bool useUR}) =>
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);

void commitTransaction({required monero.PendingTransaction transactionPointer}) {

final txCommit = monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
final txCommit = useUR
? monero.PendingTransaction_commitUR(transactionPointer, 120)
: monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);

final String? error = (() {
final status = monero.PendingTransaction_status(transactionPointer.cast());
Expand All @@ -196,6 +203,11 @@ void commitTransaction({required monero.PendingTransaction transactionPointer})
if (error != null) {
throw CreationTransactionException(message: error);
}
if (useUR) {
return txCommit as String?;
} else {
return null;
}
}

Future<PendingTransactionDescription> _createTransactionSync(Map args) async {
Expand Down
2 changes: 2 additions & 0 deletions cw_monero/lib/api/wallet_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,5 @@ Future<void> restoreFromSpendKey(
});

bool isWalletExist({required String path}) => _isWalletExist(path);

bool isViewOnlyBySpendKey() => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
32 changes: 32 additions & 0 deletions cw_monero/lib/monero_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/coins_info.dart';
import 'package:cw_monero/api/monero_output.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
Expand Down Expand Up @@ -121,6 +122,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,

@override
MoneroWalletKeys get keys => MoneroWalletKeys(
primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: monero_wallet.getSecretSpendKey(),
privateViewKey: monero_wallet.getSecretViewKey(),
publicSpendKey: monero_wallet.getPublicSpendKey(),
Expand Down Expand Up @@ -230,6 +232,36 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
}

Future<bool> submitTransactionUR(String ur) async {
final retStatus = monero.Wallet_submitTransactionUR(wptr!, ur);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw MoneroTransactionCreationException("unable to broadcast signed transaction: $err");
}
return retStatus;
}

bool importKeyImagesUR(String ur) {
final retStatus = monero.Wallet_importKeyImagesUR(wptr!, ur);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw Exception("unable to import key images: $err");
}
return retStatus;
}

String exportOutputsUR(bool all) {
final str = monero.Wallet_exportOutputsUR(wptr!, all: all);
final status = monero.Wallet_status(wptr!);
if (status != 0) {
final err = monero.Wallet_errorString(wptr!);
throw MoneroTransactionCreationException("unable to export UR: $err");
}
return str;
}

@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
Expand Down
24 changes: 23 additions & 1 deletion cw_monero/lib/pending_monero_transaction.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/transaction_history.dart'
as monero_transaction_history;
Expand Down Expand Up @@ -35,11 +36,32 @@ class PendingMoneroTransaction with PendingTransaction {
String get feeFormatted => AmountConverter.amountIntToString(
CryptoCurrency.xmr, pendingTransactionDescription.fee);

bool shouldCommitUR() => isViewOnly;

@override
Future<void> commit() async {
try {
monero_transaction_history.commitTransactionFromPointerAddress(
address: pendingTransactionDescription.pointerAddress);
address: pendingTransactionDescription.pointerAddress,
useUR: false);
} catch (e) {
final message = e.toString();

if (message.contains('Reason: double spend')) {
throw DoubleSpendException();
}

rethrow;
}
}

@override
Future<String?> commitUR() async {
try {
final ret = monero_transaction_history.commitTransactionFromPointerAddress(
address: pendingTransactionDescription.pointerAddress,
useUR: true);
return ret;
} catch (e) {
final message = e.toString();

Expand Down
5 changes: 5 additions & 0 deletions cw_nano/lib/pending_nano_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ class PendingNanoTransaction with PendingTransaction {
await nanoClient.processBlock(block, "send");
}
}

@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}
Loading

0 comments on commit cea3084

Please sign in to comment.