From 83ef61e92877dfaa7b506df2982d983162c8f0cc Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Sat, 17 Aug 2024 19:10:27 -0400 Subject: [PATCH] Cw 565 sign messages (#1378) * version bump to 3.13.9, auth working on mac * bump flutter version in workflow file * workflow fix * test fix * downgrade flutter version * test fix * test fix * update gradle version * start working on ui for message signing * updates * sign working for a few wallet types * updates & verification for electrum currencies * nano support * sign/verify working on eth, bitcoin broken * update translations * Implement Verify Message for Monero * save [skip ci] * pub key extraction working * fixes for electrum signing * verify working for solana! * electrum still not working :( [skip ci] * electrum messages working! * fixes for updated dart version, localization file updates * remove accidental inclusion * missed some unimplemented throws * Update res/values/strings_de.arb Co-authored-by: Konstantin Ullrich * Apply suggestions from code review Co-authored-by: Konstantin Ullrich * review suggestions and updates [skip ci] * [skip ci] add polygon * [skip ci] merge mac-auth/update version * fix litecoin * bio auth mac fix * remove comment and change duration from 2 to 0 * cherry pick previous changes * litecoin fixes, sign form fixes, use new walletAddressPicker * support accounts * verify messages working for monero * working sign and verify messages for nano * electrum signing working [skip ci] * additional nano fixes * update translations * attempt to decode signatures with base64 * workaround for secure storage bug on mac * bump version to 3.19.5 (because breez will need this version anyways) * some code cleanup * some changess didn't get saved * just documenting the issue [skip ci] * undo accidental removal + minor code cleanup * merge conflicts * merge fixes [skip ci] * add tron support * [wip] fixing * remove duplicate references to electrum path for maintainability * fixes * minor fix * fixes * undo debug comment * update migration for all electrum based wallets * hotfixes * copy over the rest of the fixes * minor code cleanup [skip ci] * updates * electrum signing workinggit statusgit statusgit statusgit status! * copy same fixes for litecoin * litecoin fixes * add v to litecoin signatures * fix dependencies * fix bitcoin_base version * merge fix * dep override * fix conflicts with main * trial fix for android build * fixes * fix * dep fix, should build * fix signing for bitcoin cash * [skip ci] minor code cleanup * [skip ci] minor code cleanup 2 * forgot wonero, various other fixes * more fixes * fix solana (untested) --------- Co-authored-by: Konstantin Ullrich Co-authored-by: Omar Hatem --- cw_bitcoin/lib/electrum_wallet.dart | 73 ++++++- cw_bitcoin/lib/litecoin_wallet.dart | 131 +++++++++++- cw_bitcoin/pubspec.yaml | 8 +- .../lib/src/bitcoin_cash_wallet.dart | 11 +- cw_bitcoin_cash/pubspec.yaml | 8 +- cw_core/lib/wallet_base.dart | 5 +- cw_evm/lib/evm_chain_wallet.dart | 20 +- cw_evm/pubspec.yaml | 2 + cw_monero/lib/api/wallet.dart | 4 + cw_monero/lib/monero_wallet.dart | 8 + cw_nano/lib/nano_block_info_response.dart | 37 ++++ cw_nano/lib/nano_client.dart | 87 ++++++-- cw_nano/lib/nano_wallet.dart | 25 ++- cw_nano/pubspec.lock | 8 +- cw_nano/pubspec.yaml | 3 +- cw_solana/lib/solana_wallet.dart | 54 ++++- cw_tron/lib/tron_wallet.dart | 14 +- cw_wownero/lib/api/wallet.dart | 4 + cw_wownero/lib/wownero_wallet.dart | 7 + lib/bitcoin/cw_bitcoin.dart | 4 +- lib/buy/dfx/dfx_buy_provider.dart | 4 +- lib/buy/robinhood/robinhood_buy_provider.dart | 6 +- lib/di.dart | 16 +- lib/router.dart | 14 ++ lib/routes.dart | 2 + .../cake_pay_confirm_purchase_card_page.dart | 18 +- .../dashboard/pages/cake_features_page.dart | 14 ++ lib/src/screens/dashboard/sign_page.dart | 202 ++++++++++++++++++ .../screens/dashboard/widgets/sign_form.dart | 98 +++++++++ .../dashboard/widgets/verify_form.dart | 92 ++++++++ .../screens/receive/address_list_page.dart | 31 +++ lib/src/screens/receive/receive_page.dart | 104 +-------- .../screens/receive/widgets/address_list.dart | 120 +++++++++++ lib/src/widgets/address_text_field.dart | 194 ++++++++++------- .../dashboard/dashboard_view_model.dart | 36 +++- lib/view_model/dashboard/sign_view_model.dart | 55 +++++ .../wallet_address_list_view_model.dart | 9 + lib/view_model/wallet_creation_vm.dart | 8 +- pubspec_base.yaml | 6 +- res/values/strings_ar.arb | 8 + res/values/strings_bg.arb | 8 + res/values/strings_cs.arb | 8 + res/values/strings_de.arb | 8 + res/values/strings_en.arb | 8 + res/values/strings_es.arb | 8 + res/values/strings_fr.arb | 8 + res/values/strings_ha.arb | 8 + res/values/strings_hi.arb | 8 + res/values/strings_hr.arb | 8 + res/values/strings_id.arb | 8 + res/values/strings_it.arb | 8 + res/values/strings_ja.arb | 8 + res/values/strings_ko.arb | 8 + res/values/strings_my.arb | 8 + res/values/strings_nl.arb | 8 + res/values/strings_pl.arb | 8 + res/values/strings_pt.arb | 8 + res/values/strings_ru.arb | 8 + res/values/strings_th.arb | 8 + res/values/strings_tl.arb | 8 + res/values/strings_tr.arb | 8 + res/values/strings_uk.arb | 8 + res/values/strings_ur.arb | 8 + res/values/strings_yo.arb | 8 + res/values/strings_zh.arb | 8 + 65 files changed, 1479 insertions(+), 271 deletions(-) create mode 100644 cw_nano/lib/nano_block_info_response.dart create mode 100644 lib/src/screens/dashboard/sign_page.dart create mode 100644 lib/src/screens/dashboard/widgets/sign_form.dart create mode 100644 lib/src/screens/dashboard/widgets/verify_form.dart create mode 100644 lib/src/screens/receive/address_list_page.dart create mode 100644 lib/src/screens/receive/widgets/address_list.dart create mode 100644 lib/view_model/dashboard/sign_view_model.dart diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 9dc8de083..8f2360f26 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -42,6 +42,7 @@ import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; +import 'package:http/http.dart' as http; import 'package:sp_scanner/sp_scanner.dart'; part 'electrum_wallet.g.dart'; @@ -132,6 +133,7 @@ abstract class ElectrumWalletBase final String? _mnemonic; Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0)); + Bip32Slip10Secp256k1 get sideHd => accountHD.childKey(Bip32KeyIndex(1)); final EncryptionFileUtils encryptionFileUtils; final String? passphrase; @@ -591,7 +593,7 @@ abstract class ElectrumWalletBase } final derivationPath = - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}" + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? electrum_path)}" "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" "/${utx.bitcoinAddressRecord.index}"; publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); @@ -1869,11 +1871,70 @@ abstract class ElectrumWalletBase ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index : null; final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); - final priv = ECPrivate.fromWif( - WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), - netVersion: network.wifNetVer, - ); - return priv.signMessage(StringUtils.encode(message)); + final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex()); + + String messagePrefix = '\x18Bitcoin Signed Message:\n'; + final hexEncoded = priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix); + final decodedSig = hex.decode(hexEncoded); + return base64Encode(decodedSig); + } + + @override + Future verifyMessage(String message, String signature, {String? address = null}) async { + if (address == null) { + return false; + } + + List sigDecodedBytes = []; + + if (signature.endsWith('=')) { + sigDecodedBytes = base64.decode(signature); + } else { + sigDecodedBytes = hex.decode(signature); + } + + if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) { + throw ArgumentException( + "signature must be 64 bytes without recover-id or 65 bytes with recover-id"); + } + + String messagePrefix = '\x18Bitcoin Signed Message:\n'; + final messageHash = QuickCrypto.sha256Hash( + BitcoinSignerUtils.magicMessage(utf8.encode(message), messagePrefix)); + + List correctSignature = + sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes); + List rBytes = correctSignature.sublist(0, 32); + List sBytes = correctSignature.sublist(32); + final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes)); + + List possibleRecoverIds = [0, 1]; + + final baseAddress = addressTypeFromStr(address, network); + + for (int recoveryId in possibleRecoverIds) { + final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId); + + final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes()); + + String? recoveredAddress; + + if (baseAddress is P2pkAddress) { + recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network); + } else if (baseAddress is P2pkhAddress) { + recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network); + } else if (baseAddress is P2wshAddress) { + recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network); + } else if (baseAddress is P2wpkhAddress) { + recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network); + } + + if (recoveredAddress == address) { + return true; + } + } + + return false; } Future _setInitialHeight() async { diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index d8c04dba6..890d98342 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1,6 +1,9 @@ -import 'package:bip39/bip39.dart' as bip39; +import 'dart:convert'; + import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:blockchain_utils/signer/ecdsa_signing_key.dart'; +import 'package:bip39/bip39.dart' as bip39; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; @@ -17,6 +20,9 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; +import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart'; +import 'package:pointycastle/ecc/api.dart'; +import 'package:pointycastle/ecc/curves/secp256k1.dart'; part 'litecoin_wallet.g.dart'; @@ -167,4 +173,127 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return 0; } + + @override + Future signMessage(String message, {String? address = null}) async { + final index = address != null + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index + : null; + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); + final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex()); + + final privateKey = ECDSAPrivateKey.fromBytes( + priv.toBytes(), + Curves.generatorSecp256k1, + ); + + final signature = + signLitecoinMessage(utf8.encode(message), privateKey: privateKey, bipPrive: priv.prive); + + return base64Encode(signature); + } + + List _magicPrefix(List message, List messagePrefix) { + final encodeLength = IntUtils.encodeVarint(message.length); + + return [...messagePrefix, ...encodeLength, ...message]; + } + + List signLitecoinMessage(List message, + {required ECDSAPrivateKey privateKey, required Bip32PrivateKey bipPrive}) { + String messagePrefix = '\x19Litecoin Signed Message:\n'; + final messageHash = QuickCrypto.sha256Hash(magicMessage(message, messagePrefix)); + final signingKey = EcdsaSigningKey(privateKey); + ECDSASignature ecdsaSign = + signingKey.signDigestDeterminstic(digest: messageHash, hashFunc: () => SHA256()); + final n = Curves.generatorSecp256k1.order! >> 1; + BigInt newS; + if (ecdsaSign.s.compareTo(n) > 0) { + newS = Curves.generatorSecp256k1.order! - ecdsaSign.s; + } else { + newS = ecdsaSign.s; + } + final rawSig = ECDSASignature(ecdsaSign.r, newS); + final rawSigBytes = rawSig.toBytes(BitcoinSignerUtils.baselen); + + final pub = bipPrive.publicKey; + final ECDomainParameters curve = ECCurve_secp256k1(); + final point = curve.curve.decodePoint(pub.point.toBytes()); + + final rawSigEc = ECSignature(rawSig.r, rawSig.s); + + final recId = SignUtils.findRecoveryId( + SignUtils.getHexString(messageHash, offset: 0, length: messageHash.length), + rawSigEc, + Uint8List.fromList(pub.uncompressed), + ); + + final v = recId + 27 + (point!.isCompressed ? 4 : 0); + + final combined = Uint8List.fromList([v, ...rawSigBytes]); + + return combined; + } + + List magicMessage(List message, String messagePrefix) { + final prefixBytes = StringUtils.encode(messagePrefix); + final magic = _magicPrefix(message, prefixBytes); + return QuickCrypto.sha256Hash(magic); + } + + @override + Future verifyMessage(String message, String signature, {String? address = null}) async { + if (address == null) { + return false; + } + + List sigDecodedBytes = []; + + if (signature.endsWith('=')) { + sigDecodedBytes = base64.decode(signature); + } else { + sigDecodedBytes = hex.decode(signature); + } + + if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) { + throw ArgumentException( + "litecoin signature must be 64 bytes without recover-id or 65 bytes with recover-id"); + } + + String messagePrefix = '\x19Litecoin Signed Message:\n'; + final messageHash = QuickCrypto.sha256Hash(magicMessage(utf8.encode(message), messagePrefix)); + + List correctSignature = + sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes); + List rBytes = correctSignature.sublist(0, 32); + List sBytes = correctSignature.sublist(32); + final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes)); + + List possibleRecoverIds = [0, 1]; + + final baseAddress = addressTypeFromStr(address, network); + + for (int recoveryId in possibleRecoverIds) { + final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId); + final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes()); + + String? recoveredAddress; + + if (baseAddress is P2pkAddress) { + recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network); + } else if (baseAddress is P2pkhAddress) { + recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network); + } else if (baseAddress is P2wshAddress) { + recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network); + } else if (baseAddress is P2wpkhAddress) { + recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network); + } + + if (recoveredAddress == address) { + return true; + } + } + + return false; + } } diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 449833220..2af1ac54e 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -25,10 +25,6 @@ dependencies: ref: Add-Support-For-OP-Return-data rxdart: ^0.27.5 cryptography: ^2.0.5 - bitcoin_base: - git: - url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v4 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils @@ -57,6 +53,10 @@ dependency_overrides: url: https://github.com/cake-tech/ledger-flutter.git ref: cake-v3 watcher: ^1.1.0 + bitcoin_base: + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v5 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index a59569ae6..5659528c0 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -202,11 +202,12 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { @override Future signMessage(String message, {String? address = null}) async { - final index = address != null - ? walletAddresses.allAddresses - .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) - .index - : null; + int? index; + try { + index = address != null + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index + : null; + } catch (_) {} final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); final priv = ECPrivate.fromWif( WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 3728bafc5..64bd38b1d 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -25,10 +25,6 @@ dependencies: git: url: https://github.com/cake-tech/bitbox-flutter.git ref: Add-Support-For-OP-Return-data - bitcoin_base: - git: - url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v4 blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils @@ -43,6 +39,10 @@ dev_dependencies: dependency_overrides: watcher: ^1.1.0 + bitcoin_base: + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v5 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index f7af15224..14ba898a7 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -69,7 +69,6 @@ abstract class WalletBase renameWalletFiles(String newWalletName); - Future signMessage(String message, {String? address = null}) => throw UnimplementedError(); + Future signMessage(String message, {String? address = null}); + + Future verifyMessage(String message, String signature, {String? address = null}); bool? isTestnet; } diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 80a366e6f..bbf972f0d 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -35,6 +35,7 @@ import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:web3dart/crypto.dart'; import 'package:web3dart/web3dart.dart'; +import 'package:eth_sig_util/eth_sig_util.dart'; import 'evm_chain_transaction_info.dart'; import 'evm_erc20_balance.dart'; @@ -500,7 +501,7 @@ abstract class EVMChainWalletBase } final methodSignature = - transactionInput.length >= 10 ? transactionInput.substring(0, 10) : null; + transactionInput.length >= 10 ? transactionInput.substring(0, 10) : null; return methodSignatureToType[methodSignature]; } @@ -692,8 +693,21 @@ abstract class EVMChainWalletBase } @override - Future signMessage(String message, {String? address}) async => - bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message))); + Future signMessage(String message, {String? address}) async { + return bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message))); + } + + @override + Future verifyMessage(String message, String signature, {String? address}) async { + if (address == null) { + return false; + } + final recoveredAddress = EthSigUtil.recoverPersonalSignature( + message: ascii.encode(message), + signature: signature, + ); + return recoveredAddress.toUpperCase() == address.toUpperCase(); + } Web3Client? getWeb3Client() => _client.getWeb3Client(); diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index b24e375a7..3e12834b1 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -13,6 +13,8 @@ dependencies: flutter: sdk: flutter web3dart: ^2.7.1 + eth_sig_util: ^0.0.9 + erc20: ^1.0.1 bip39: ^1.0.6 bip32: ^2.0.0 hex: ^0.2.0 diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 1a6e5315d..b493e536e 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -316,3 +316,7 @@ Future trustedDaemon() async => monero.Wallet_trustedDaemon(wptr!); String signMessage(String message, {String address = ""}) { return monero.Wallet_signMessage(wptr!, message: message, address: address); } + +bool verifyMessage(String message, String address, String signature) { + return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); +} \ No newline at end of file diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 31e09ca2d..f5fa0ec7e 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -783,4 +783,12 @@ abstract class MoneroWalletBase extends WalletBase verifyMessage(String message, String signature, {String? address = null}) async { + if (address == null) return false; + + return monero_wallet.verifyMessage(message, address, signature); + } + } diff --git a/cw_nano/lib/nano_block_info_response.dart b/cw_nano/lib/nano_block_info_response.dart new file mode 100644 index 000000000..d2f000b9d --- /dev/null +++ b/cw_nano/lib/nano_block_info_response.dart @@ -0,0 +1,37 @@ +class BlockContentsResponse { + String type; + String account; + String previous; + String representative; + String balance; + String link; + String linkAsAccount; + String signature; + String work; + + BlockContentsResponse({ + required this.type, + required this.account, + required this.previous, + required this.representative, + required this.balance, + required this.link, + required this.linkAsAccount, + required this.signature, + required this.work, + }); + + factory BlockContentsResponse.fromJson(Map json) { + return BlockContentsResponse( + type: json['type'] as String, + account: json['account'] as String, + previous: json['previous'] as String, + representative: json['representative'] as String, + balance: json['balance'] as String, + link: json['link'] as String, + linkAsAccount: json['link_as_account'] as String, + signature: json['signature'] as String, + work: json['work'] as String, + ); + } +} diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 8d8bef13d..478a6c125 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'dart:convert'; import 'package:cw_core/nano_account_info_response.dart'; +import 'package:cw_nano/nano_block_info_response.dart'; import 'package:cw_core/n2_node.dart'; import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_transaction_model.dart'; import 'package:http/http.dart' as http; -import 'package:nanodart/nanodart.dart'; import 'package:cw_core/node.dart'; import 'package:nanoutil/nanoutil.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -111,6 +111,27 @@ class NanoClient { } } + Future getBlockContents(String block) async { + try { + final response = await http.post( + _node!.uri, + headers: CAKE_HEADERS, + body: jsonEncode( + { + "action": "block_info", + "json_block": "true", + "hash": block, + }, + ), + ); + final data = await jsonDecode(response.body); + return BlockContentsResponse.fromJson(data["contents"] as Map); + } catch (e) { + print("error while getting block info $e"); + return null; + } + } + Future changeRep({ required String privateKey, required String repAddress, @@ -135,8 +156,8 @@ class NanoClient { }; // sign the change block: - final String hash = NanoBlocks.computeStateHash( - NanoAccountType.NANO, + final String hash = NanoSignatures.computeStateHash( + NanoBasedCurrency.NANO, changeBlock["account"]!, changeBlock["previous"]!, changeBlock["representative"]!, @@ -248,7 +269,7 @@ class NanoClient { } final String representative = infoResponse.representative; // link = destination address: - final String link = NanoAccounts.extractPublicKey(destinationAddress); + final String link = NanoDerivations.addressToPublicKey(destinationAddress); final String linkAsAccount = destinationAddress; // construct the send block: @@ -262,8 +283,8 @@ class NanoClient { }; // sign the send block: - final String hash = NanoBlocks.computeStateHash( - NanoAccountType.NANO, + final String hash = NanoSignatures.computeStateHash( + NanoBasedCurrency.NANO, sendBlock["account"]!, sendBlock["previous"]!, sendBlock["representative"]!, @@ -285,7 +306,6 @@ class NanoClient { Future receiveBlock({ required String blockHash, - required String source, required String amountRaw, required String destinationAddress, required String privateKey, @@ -310,15 +330,56 @@ class NanoClient { representative = infoData.representative; } + if ((BigInt.tryParse(amountRaw) ?? BigInt.zero) <= BigInt.zero) { + throw Exception("amountRaw must be greater than zero"); + } + + BlockContentsResponse? frontierContents; + + if (!openBlock) { + // get the block info of the frontier block: + frontierContents = await getBlockContents(frontier); + + if (frontierContents == null) { + throw Exception("error while getting frontier block info"); + } + + final String frontierHash = NanoSignatures.computeStateHash( + NanoBasedCurrency.NANO, + frontierContents.account, + frontierContents.previous, + frontierContents.representative, + BigInt.parse(frontierContents.balance), + frontierContents.link, + ); + + bool valid = await NanoSignatures.verify( + frontierHash, + frontierContents.signature, + destinationAddress, + ); + + if (!valid) { + throw Exception( + "Frontier block signature is invalid! Potentially malicious block detected!"); + } + } + // first get the account balance: - final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance; + late BigInt currentBalance; + if (!openBlock) { + currentBalance = BigInt.parse(frontierContents!.balance); + } else { + currentBalance = BigInt.zero; + } final BigInt txAmount = BigInt.parse(amountRaw); final BigInt balanceAfterTx = currentBalance + txAmount; // link = send block hash: final String link = blockHash; // this "linkAsAccount" is meaningless: - final String linkAsAccount = NanoAccounts.createAccount(NanoAccountType.NANO, blockHash); + final String linkAsAccount = + NanoDerivations.publicKeyToAddress(blockHash, currency: NanoBasedCurrency.NANO); // construct the receive block: Map receiveBlock = { @@ -332,8 +393,8 @@ class NanoClient { }; // sign the receive block: - final String hash = NanoBlocks.computeStateHash( - NanoAccountType.NANO, + final String hash = NanoSignatures.computeStateHash( + NanoBasedCurrency.NANO, receiveBlock["account"]!, receiveBlock["previous"]!, receiveBlock["representative"]!, @@ -345,7 +406,7 @@ class NanoClient { // get PoW for the receive block: String? work; if (openBlock) { - work = await requestWork(NanoAccounts.extractPublicKey(destinationAddress)); + work = await requestWork(NanoDerivations.addressToPublicKey(destinationAddress)); } else { work = await requestWork(frontier); } @@ -409,10 +470,8 @@ class NanoClient { for (final blockHash in blocks.keys) { final block = blocks[blockHash]; final String amountRaw = block["amount"] as String; - final String source = block["source"] as String; await receiveBlock( blockHash: blockHash, - source: source, amountRaw: amountRaw, privateKey: privateKey, destinationAddress: destinationAddress, diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index cba8d09a0..700710c2e 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -27,7 +27,6 @@ import 'package:cw_nano/nano_wallet_addresses.dart'; import 'package:cw_nano/nano_wallet_keys.dart'; import 'package:cw_nano/pending_nano_transaction.dart'; import 'package:mobx/mobx.dart'; -import 'package:nanodart/nanodart.dart'; import 'package:nanoutil/nanoutil.dart'; part 'nano_wallet.g.dart'; @@ -107,7 +106,6 @@ abstract class NanoWalletBase if (_derivationType == DerivationType.unknown) { _derivationType = DerivationType.nano; } - final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; // our "mnemonic" is actually a hex form seed: if (!_mnemonic.contains(' ')) { @@ -122,8 +120,10 @@ abstract class NanoWalletBase _hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' ')); } } - NanoDerivationType derivationType = - type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD; + + final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; + NanoDerivationType derivationType = NanoDerivations.stringToType(type); + _privateKey = await NanoDerivations.universalSeedToPrivate( _hexSeed!, index: 0, @@ -216,8 +216,8 @@ abstract class NanoWalletBase balanceAfterTx: runningBalance, previousHash: previousHash, ); - previousHash = NanoBlocks.computeStateHash( - NanoAccountType.NANO, + previousHash = NanoSignatures.computeStateHash( + NanoBasedCurrency.NANO, block["account"]!, block["previous"]!, block["representative"]!, @@ -535,4 +535,17 @@ abstract class NanoWalletBase // Delete old name's dir and files await Directory(currentDirPath).delete(recursive: true); } + + @override + Future signMessage(String message, {String? address = null}) async { + return NanoSignatures.signMessage(message, privateKey!); + } + + @override + Future verifyMessage(String message, String signature, {String? address = null}) async { + if (address == null) { + return false; + } + return await NanoSignatures.verifyMessage(message, signature, address); + } } diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index bbe909199..ef9de14f9 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -513,7 +513,7 @@ packages: source: hosted version: "2.3.0" nanodart: - dependency: "direct main" + dependency: transitive description: name: nanodart sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282" @@ -524,11 +524,11 @@ packages: dependency: "direct main" description: path: "." - ref: c37e72817cf0a28162f43124f79661d6c8e0098f - resolved-ref: c37e72817cf0a28162f43124f79661d6c8e0098f + ref: c01a9c552917008d8fbc6b540db657031625b04f + resolved-ref: c01a9c552917008d8fbc6b540db657031625b04f url: "https://github.com/perishllc/nanoutil.git" source: git - version: "1.0.0" + version: "1.0.3" package_config: dependency: transitive description: diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml index 6fae6a895..3ddd9769e 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -15,7 +15,6 @@ dependencies: mobx: ^2.0.7+4 bip39: ^1.0.6 bip32: ^2.0.0 - nanodart: ^2.0.0 decimal: ^2.3.3 libcrypto: ^0.2.2 ed25519_hd_key: ^2.2.0 @@ -25,7 +24,7 @@ dependencies: nanoutil: git: url: https://github.com/perishllc/nanoutil.git - ref: c37e72817cf0a28162f43124f79661d6c8e0098f + ref: c01a9c552917008d8fbc6b540db657031625b04f cw_core: path: ../cw_core diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 66b8bca42..4e69db3b8 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -32,6 +32,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:solana/base58.dart'; import 'package:solana/metaplex.dart' as metaplex; import 'package:solana/solana.dart'; +import 'package:solana/src/crypto/ed25519_hd_keypair.dart'; +import 'package:cryptography/cryptography.dart'; part 'solana_wallet.g.dart'; @@ -571,17 +573,59 @@ abstract class SolanaWalletBase }); } - Future signSolanaMessage(String message) async { + @override + Future signMessage(String message, {String? address}) async { // Convert the message to bytes final messageBytes = utf8.encode(message); // Sign the message bytes with the wallet's private key - final signature = await _walletKeyPair!.sign(messageBytes); + final signature = (await _walletKeyPair!.sign(messageBytes)).toString(); - // Convert the signature to a hexadecimal string - final hex = HEX.encode(signature.bytes); + return HEX.encode(utf8.encode(signature)).toUpperCase(); + } - return hex; + List> bytesFromSigString(String signatureString) { + final regex = RegExp(r'Signature\(\[(.+)\], publicKey: (.+)\)'); + final match = regex.firstMatch(signatureString); + + if (match != null) { + final bytesString = match.group(1)!; + final base58EncodedPublicKeyString = match.group(2)!; + final sigBytes = bytesString.split(', ').map(int.parse).toList(); + + List pubKeyBytes = base58decode(base58EncodedPublicKeyString); + + return [sigBytes, pubKeyBytes]; + } else { + throw const FormatException('Invalid Signature string format'); + } + } + + @override + Future verifyMessage(String message, String signature, {String? address}) async { + String signatureString = utf8.decode(HEX.decode(signature)); + + List> bytes = bytesFromSigString(signatureString); + + final messageBytes = utf8.encode(message); + final sigBytes = bytes[0]; + final pubKeyBytes = bytes[1]; + + if (address == null) { + return false; + } + + // make sure the address derived from the public key provided matches the one we expect + final pub = Ed25519HDPublicKey(pubKeyBytes); + if (address != pub.toBase58()) { + return false; + } + + return await verifySignature( + message: messageBytes, + signature: sigBytes, + publicKey: Ed25519HDPublicKey(pubKeyBytes), + ); } SolanaClient? get solanaClient => _client.getSolanaClient; diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 7dc43b4bb..f5841d894 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -580,8 +580,18 @@ abstract class TronWalletBase } @override - Future signMessage(String message, {String? address}) async => - _tronPrivateKey.signPersonalMessage(ascii.encode(message)); + Future signMessage(String message, {String? address}) async { + return _tronPrivateKey.signPersonalMessage(ascii.encode(message)); + } + + @override + Future verifyMessage(String message, String signature, {String? address}) async { + if (address == null) { + return false; + } + TronPublicKey pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!; + return pubKey.toAddress().toString() == address; + } String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress(); diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart index 0a06a9950..56f54dfac 100644 --- a/cw_wownero/lib/api/wallet.dart +++ b/cw_wownero/lib/api/wallet.dart @@ -320,3 +320,7 @@ Future trustedDaemon() async => wownero.Wallet_trustedDaemon(wptr!); String signMessage(String message, {String address = ""}) { return wownero.Wallet_signMessage(wptr!, message: message, address: address); } + +bool verifyMessage(String message, String address, String signature) { + return wownero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); +} \ No newline at end of file diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index 85f5e4b2f..c3f4bcb69 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -743,4 +743,11 @@ abstract class WowneroWalletBase final useAddress = address ?? ""; return wownero_wallet.signMessage(message, address: useAddress); } + + @override + Future verifyMessage(String message, String signature, {String? address = null}) async { + if (address == null) return false; + + return wownero_wallet.verifyMessage(message, address, signature); + } } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 989cd2b35..e87773f97 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -275,7 +275,7 @@ class CWBitcoin extends Bitcoin { return [DerivationType.bip39, DerivationType.electrum]; } - int _countOccurrences(String str, String charToCount) { + int _countCharOccurrences(String str, String charToCount) { int count = 0; for (int i = 0; i < str.length; i++) { if (str[i] == charToCount) { @@ -330,7 +330,7 @@ class CWBitcoin extends Bitcoin { ); String balancePath = dInfoCopy.derivationPath!; - int derivationDepth = _countOccurrences(balancePath, "/"); + int derivationDepth = _countCharOccurrences(balancePath, '/'); // for BIP44 if (derivationDepth == 3 || derivationDepth == 1) { diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 2a7e2ab13..b3ed72498 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -124,12 +124,12 @@ class DFXBuyProvider extends BuyProvider { switch (wallet.type) { case WalletType.ethereum: case WalletType.polygon: - return wallet.signMessage(message); + return await wallet.signMessage(message); case WalletType.monero: case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return wallet.signMessage(message, address: walletAddress); + return await wallet.signMessage(message, address: walletAddress); default: throw Exception("WalletType is not available for DFX ${wallet.type}"); } diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index ab58754dd..2d809772e 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -37,15 +37,15 @@ class RobinhoodBuyProvider extends BuyProvider { String get _apiSecret => secrets.exchangeHelperApiKey; - Future getSignature(String message) { + Future getSignature(String message) async { switch (wallet.type) { case WalletType.ethereum: case WalletType.polygon: - return wallet.signMessage(message); + return await wallet.signMessage(message); case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return wallet.signMessage(message, address: wallet.walletAddresses.address); + return await wallet.signMessage(message, address: wallet.walletAddresses.address); default: throw Exception("WalletType is not available for Robinhood ${wallet.type}"); } diff --git a/lib/di.dart b/lib/di.dart index 7c22e809c..1967c9227 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -30,6 +30,12 @@ import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; +import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; +import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; +import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/template.dart'; @@ -159,7 +165,6 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/node.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; -import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; @@ -179,7 +184,6 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart'; import 'package:cake_wallet/store/templates/send_template_store.dart'; import 'package:cake_wallet/store/wallet_list_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/auth_view_model.dart'; import 'package:cake_wallet/view_model/backup_view_model.dart'; import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart'; @@ -193,7 +197,6 @@ import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; -import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart'; @@ -224,7 +227,6 @@ import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart'; import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/receive_page_option.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; @@ -853,6 +855,8 @@ Future setup({ getIt.registerFactoryParam( (ContactRecord? contact, _) => ContactPage(getIt.get(param1: contact))); + getIt.registerFactory(() => AddressListPage(getIt.get())); + getIt.registerFactory(() { final appStore = getIt.get(); return NodeListViewModel(_nodeSource, appStore); @@ -1271,9 +1275,11 @@ Future setup({ getIt.registerFactory( () => WalletConnectConnectionsView(web3walletService: getIt.get())); - + getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); + getIt.registerFactory(() => SignViewModel(getIt.get().wallet!)); + _isSetupFinished = true; } diff --git a/lib/router.dart b/lib/router.dart index 498077511..25af39043 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -27,6 +27,7 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart'; import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; @@ -42,6 +43,8 @@ import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart'; import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart'; +import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; +import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; @@ -99,6 +102,7 @@ import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; @@ -465,6 +469,9 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(param1: selectedCurrency)); + case Routes.pickerWalletAddress: + return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.addressBookAddContact: return CupertinoPageRoute( builder: (_) => getIt.get(param1: settings.arguments as ContactRecord?)); @@ -696,6 +703,13 @@ Route createRoute(RouteSettings settings) { case Routes.torPage: return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.signPage: + return MaterialPageRoute( + builder: (_) => SignPage( + getIt.get(), + ), + ); + case Routes.connectDevices: final params = settings.arguments as ConnectDevicePageParams; return MaterialPageRoute( diff --git a/lib/routes.dart b/lib/routes.dart index caa7eb39e..9c421cab5 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -31,6 +31,7 @@ class Routes { static const nanoAccountCreation = '/nano_account_new'; static const addressBook = '/address_book'; static const pickerAddressBook = '/picker_address_book'; + static const pickerWalletAddress = '/picker_wallet_address'; static const addressBookAddContact = '/address_book_add_contact'; static const showKeys = '/show_keys'; static const exchangeConfirm = '/exchange_confirm'; @@ -103,5 +104,6 @@ class Routes { static const nftDetailsPage = '/nft_details_page'; static const importNFTPage = '/import_nft_page'; static const torPage = '/tor_page'; + static const signPage = '/sign_page'; static const connectDevices = '/device/connect'; } diff --git a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart index 02ddf037d..81f6a354f 100644 --- a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart +++ b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart @@ -322,31 +322,31 @@ class CakePayBuyCardDetailPage extends BasePage { await showPopUp( context: context, - builder: (_) { + builder: (popupContext) { return Observer( builder: (_) => ConfirmSendingAlert( - alertTitle: S.of(context).confirm_sending, - paymentId: S.of(context).payment_id, + alertTitle: S.of(popupContext).confirm_sending, + paymentId: S.of(popupContext).payment_id, paymentIdValue: order?.orderId, expirationTime: cakePayPurchaseViewModel.formattedRemainingTime, onDispose: () => _handleDispose(disposer), - amount: S.of(context).send_amount, + amount: S.of(popupContext).send_amount, amountValue: pendingTransaction.amountFormatted, fiatAmountValue: cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted, - fee: S.of(context).send_fee, + fee: S.of(popupContext).send_fee, feeValue: pendingTransaction.feeFormatted, feeFiatAmount: cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted, feeRate: pendingTransaction.feeRate, outputs: cakePayPurchaseViewModel.sendViewModel.outputs, - rightButtonText: S.of(context).send, - leftButtonText: S.of(context).cancel, + rightButtonText: S.of(popupContext).send, + leftButtonText: S.of(popupContext).cancel, actionRightButton: () async { - Navigator.of(context).pop(); + Navigator.of(popupContext).pop(); await cakePayPurchaseViewModel.sendViewModel.commitTransaction(); }, - actionLeftButton: () => Navigator.of(context).pop())); + actionLeftButton: () => Navigator.of(popupContext).pop())); }, ); } diff --git a/lib/src/screens/dashboard/pages/cake_features_page.dart b/lib/src/screens/dashboard/pages/cake_features_page.dart index b034fb799..0c953c892 100644 --- a/lib/src/screens/dashboard/pages/cake_features_page.dart +++ b/lib/src/screens/dashboard/pages/cake_features_page.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -64,6 +65,19 @@ class CakeFeaturesPage extends StatelessWidget { subTitle: S.of(context).nanogpt_subtitle, onTap: () => _launchUrl("cake.nano-gpt.com"), ), + SizedBox(height: 20), + Observer( + builder: (context) { + if (!dashboardViewModel.hasSignMessages) { + return const SizedBox(); + } + return DashBoardRoundedCardWidget( + onTap: () => Navigator.of(context).pushNamed(Routes.signPage), + title: S.current.sign_verify_message, + subTitle: S.current.sign_verify_message_sub, + ); + }, + ), ], ), ), diff --git a/lib/src/screens/dashboard/sign_page.dart b/lib/src/screens/dashboard/sign_page.dart new file mode 100644 index 000000000..05cf63728 --- /dev/null +++ b/lib/src/screens/dashboard/sign_page.dart @@ -0,0 +1,202 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/sign_form.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/verify_form.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; +import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:mobx/mobx.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart'; + +class SignPage extends BasePage { + SignPage(this.signViewModel) + : signFormKey = GlobalKey(), + verifyFormKey = GlobalKey(), + _pages = [], + _controller = PageController(initialPage: 0) { + _pages.add(SignForm( + key: signFormKey, + type: signViewModel.wallet.type, + includeAddress: signViewModel.signIncludesAddress, + )); + _pages.add(VerifyForm( + key: verifyFormKey, + type: signViewModel.wallet.type, + )); + } + + @override + Widget middle(BuildContext context) => Observer( + builder: (_) => Text( + S.current.sign_verify_title, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + fontFamily: 'Lato', + color: titleColor(context), + ), + )); + + final SignViewModel signViewModel; + final PageController _controller; + final List _pages; + final GlobalKey signFormKey; + final GlobalKey verifyFormKey; + bool _isEffectsInstalled = false; + + @override + Widget body(BuildContext context) { + _setEffects(context); + + return KeyboardActions( + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: FocusNode(), + toolbarButtons: [(_) => KeyboardDoneButton()], + ) + ], + ), + child: Container( + height: 0, + color: Theme.of(context).colorScheme.background, + child: Center( + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: PageView.builder( + onPageChanged: (page) { + signViewModel.isSigning = page == 0; + }, + controller: _controller, + itemCount: _pages.length, + itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]), + ), + ), + if (_pages.length > 1) + Padding( + padding: EdgeInsets.only(top: 10), + child: SmoothPageIndicator( + controller: _controller, + count: _pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).hintColor.withOpacity(0.5), + activeDotColor: Theme.of(context).hintColor, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24), + child: Column( + children: [ + Observer( + builder: (context) { + return LoadingPrimaryButton( + onPressed: () async { + await _confirmForm(context); + }, + text: signViewModel.isSigning + ? S.current.sign_message + : S.current.verify_message, + color: Theme.of(context) + .extension()! + .createNewWalletButtonBackgroundColor, + textColor: Theme.of(context) + .extension()! + .restoreWalletButtonTextColor, + isLoading: signViewModel.state is IsExecutingState, + isDisabled: signViewModel.state is IsExecutingState, + ); + }, + ), + ], + ), + ) + ], + ), + ), + ), + ), + ); + } + + void _setEffects(BuildContext context) async { + if (_isEffectsInstalled) { + return; + } + _isEffectsInstalled = true; + + reaction((_) => signViewModel.state, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: S.current.error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }); + }); + } + if (state is ExecutedSuccessfullyState) { + if (signViewModel.isSigning) { + signFormKey.currentState!.signatureController.text = state.payload as String; + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: S.current.successful, + alertContent: S.current.message_verified, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }); + }); + } + } + }); + } + + Future _confirmForm(BuildContext context) async { + FocusManager.instance.primaryFocus?.unfocus(); + + if (signViewModel.isSigning) { + String message = signFormKey.currentState!.messageController.text; + String? address; + if (signViewModel.signIncludesAddress) { + address = signFormKey.currentState!.addressController.text; + } + await signViewModel.sign(message, address: address); + } else { + String message = verifyFormKey.currentState!.messageController.text; + String signature = verifyFormKey.currentState!.signatureController.text; + String address = verifyFormKey.currentState!.addressController.text; + await signViewModel.verify(message, signature, address: address); + } + } +} diff --git a/lib/src/screens/dashboard/widgets/sign_form.dart b/lib/src/screens/dashboard/widgets/sign_form.dart new file mode 100644 index 000000000..c0f8ba328 --- /dev/null +++ b/lib/src/screens/dashboard/widgets/sign_form.dart @@ -0,0 +1,98 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class SignForm extends StatefulWidget { + SignForm({ + Key? key, + required this.type, + required this.includeAddress, + }) : super(key: key); + + final WalletType type; + final bool includeAddress; + + @override + SignFormState createState() => SignFormState(); +} + +class SignFormState extends State { + SignFormState() + : formKey = GlobalKey(), + messageController = TextEditingController(), + addressController = TextEditingController(), + signatureController = TextEditingController(); + + final TextEditingController messageController; + final TextEditingController addressController; + final TextEditingController signatureController; + final GlobalKey formKey; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Column( + children: [ + Form( + key: formKey, + child: Column( + children: [ + AddressTextField( + controller: messageController, + placeholder: S.current.message, + options: [AddressTextFieldOption.paste], + buttonColor: Theme.of(context).hintColor, + ), + if (widget.includeAddress) ...[ + const SizedBox(height: 20), + AddressTextField( + controller: addressController, + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.walletAddresses + ], + buttonColor: Theme.of(context).hintColor, + onSelectedContact: (contact) { + addressController.text = contact.address; + }, + selectedCurrency: walletTypeToCryptoCurrency(widget.type), + ), + ], + ], + )), + const SizedBox(height: 20), + GestureDetector( + onTap: () async { + final text = signatureController.text; + if (text.isEmpty) { + return; + } + Clipboard.setData(ClipboardData(text: text)); + showBar(context, S.of(context).transaction_details_copied(text)); + }, + child: BaseTextFormField( + enabled: false, + controller: signatureController, + hintText: S.current.signature, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/dashboard/widgets/verify_form.dart b/lib/src/screens/dashboard/widgets/verify_form.dart new file mode 100644 index 000000000..d59261494 --- /dev/null +++ b/lib/src/screens/dashboard/widgets/verify_form.dart @@ -0,0 +1,92 @@ +import 'package:cake_wallet/core/wallet_name_validator.dart'; +import 'package:cake_wallet/entities/generate_name.dart'; +import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; +import 'package:cake_wallet/src/widgets/seed_widget.dart'; +import 'package:cake_wallet/themes/extensions/address_theme.dart'; +import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mobx/mobx.dart'; +import 'package:polyseed/polyseed.dart'; + +class VerifyForm extends StatefulWidget { + VerifyForm({ + Key? key, + required this.type, + }) : super(key: key); + + final WalletType type; + + @override + VerifyFormState createState() => VerifyFormState(); +} + +class VerifyFormState extends State { + VerifyFormState() + : formKey = GlobalKey(), + messageController = TextEditingController(), + addressController = TextEditingController(), + signatureController = TextEditingController(); + + final TextEditingController messageController; + final TextEditingController addressController; + final TextEditingController signatureController; + final GlobalKey formKey; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(left: 24, right: 24), + child: Form( + key: formKey, + child: Column( + children: [ + AddressTextField( + controller: messageController, + placeholder: S.current.message, + options: [AddressTextFieldOption.paste], + buttonColor: Theme.of(context).hintColor, + ), + const SizedBox(height: 20), + AddressTextField( + controller: addressController, + options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses], + buttonColor: Theme.of(context).hintColor, + onSelectedContact: (contact) { + addressController.text = contact.address; + }, + selectedCurrency: walletTypeToCryptoCurrency(widget.type), + ), + const SizedBox(height: 20), + AddressTextField( + controller: signatureController, + placeholder: S.current.signature, + options: [AddressTextFieldOption.paste], + buttonColor: Theme.of(context).hintColor, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/receive/address_list_page.dart b/lib/src/screens/receive/address_list_page.dart new file mode 100644 index 000000000..5f6794715 --- /dev/null +++ b/lib/src/screens/receive/address_list_page.dart @@ -0,0 +1,31 @@ +import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class AddressListPage extends BasePage { + AddressListPage(this.addressListViewModel); + + final WalletAddressListViewModel addressListViewModel; + + @override + String get title => S.current.accounts_subaddresses; + + @override + Widget body(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + AddressList( + addressListViewModel: addressListViewModel, + onSelect: (String address) async { + Navigator.of(context).pop(address); + }, + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 03524ef79..724e5c3bd 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; @@ -122,108 +123,7 @@ class ReceivePage extends BasePage { amountController: _amountController, isLight: currentTheme.type == ThemeType.light), ), - Observer( - builder: (_) => ListView.separated( - padding: EdgeInsets.all(0), - separatorBuilder: (context, _) => const HorizontalSectionDivider(), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: addressListViewModel.items.length, - itemBuilder: (context, index) { - final item = addressListViewModel.items[index]; - Widget cell = Container(); - - if (item is WalletAccountListHeader) { - cell = HeaderTile( - showTrailingButton: true, - walletAddressListViewModel: addressListViewModel, - trailingButtonTap: () async { - if (addressListViewModel.type == WalletType.monero || - addressListViewModel.type == WalletType.wownero || - addressListViewModel.type == WalletType.haven) { - await showPopUp( - context: context, - builder: (_) => getIt.get()); - } else { - await showPopUp( - context: context, - builder: (_) => getIt.get()); - } - }, - title: S.of(context).accounts, - trailingIcon: Icon( - Icons.arrow_forward_ios, - size: 14, - color: Theme.of(context).extension()!.iconsColor, - )); - } - - if (item is WalletAddressListHeader) { - final hasTitle = item.title != null; - - cell = HeaderTile( - title: hasTitle ? item.title! : S.of(context).addresses, - walletAddressListViewModel: addressListViewModel, - showTrailingButton: - !addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle, - showSearchButton: true, - trailingButtonTap: () => - Navigator.of(context).pushNamed(Routes.newSubaddress), - trailingIcon: hasTitle - ? null - : Icon( - Icons.add, - size: 20, - color: - Theme.of(context).extension()!.iconsColor, - ), - ); - } - - if (item is WalletAddressListItem) { - cell = Observer(builder: (_) { - final isCurrent = item.address == addressListViewModel.address.address; - final backgroundColor = isCurrent - ? Theme.of(context) - .extension()! - .currentTileBackgroundColor - : Theme.of(context) - .extension()! - .tilesBackgroundColor; - final textColor = isCurrent - ? Theme.of(context) - .extension()! - .currentTileTextColor - : Theme.of(context).extension()!.tilesTextColor; - - return AddressCell.fromItem( - item, - isCurrent: isCurrent, - hasBalance: addressListViewModel.isElectrumWallet, - backgroundColor: backgroundColor, - textColor: textColor, - onTap: item.isOneTimeReceiveAddress == true - ? null - : (_) => addressListViewModel.setAddress(item), - onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary - ? null - : () => Navigator.of(context) - .pushNamed(Routes.newSubaddress, arguments: item), - onDelete: !addressListViewModel.isSilentPayments || item.isPrimary - ? null - : () => addressListViewModel.deleteAddress(item), - ); - }); - } - - return index != 0 - ? cell - : ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), topRight: Radius.circular(30)), - child: cell, - ); - })), + AddressList(addressListViewModel: addressListViewModel), Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), child: Text( diff --git a/lib/src/screens/receive/widgets/address_list.dart b/lib/src/screens/receive/widgets/address_list.dart new file mode 100644 index 000000000..8dfbedec1 --- /dev/null +++ b/lib/src/screens/receive/widgets/address_list.dart @@ -0,0 +1,120 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart'; +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; +import 'package:cake_wallet/src/widgets/section_divider.dart'; +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class AddressList extends StatelessWidget { + const AddressList({ + super.key, + required this.addressListViewModel, + this.onSelect, + }); + + final WalletAddressListViewModel addressListViewModel; + final Function(String)? onSelect; + + @override + Widget build(BuildContext context) { + bool editable = onSelect == null; + return Observer( + builder: (_) => ListView.separated( + padding: EdgeInsets.all(0), + separatorBuilder: (context, _) => const HorizontalSectionDivider(), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: addressListViewModel.items.length, + itemBuilder: (context, index) { + final item = addressListViewModel.items[index]; + Widget cell = Container(); + + if (item is WalletAccountListHeader) { + cell = HeaderTile( + showTrailingButton: true, + walletAddressListViewModel: addressListViewModel, + trailingButtonTap: () async { + if (addressListViewModel.type == WalletType.monero || + addressListViewModel.type == WalletType.haven) { + await showPopUp( + context: context, builder: (_) => getIt.get()); + } else { + await showPopUp( + context: context, builder: (_) => getIt.get()); + } + }, + title: S.of(context).accounts, + trailingIcon: Icon( + Icons.arrow_forward_ios, + size: 14, + color: Theme.of(context).extension()!.iconsColor, + )); + } + + if (item is WalletAddressListHeader) { + cell = HeaderTile( + title: S.of(context).addresses, + walletAddressListViewModel: addressListViewModel, + showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled, + showSearchButton: true, + trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress), + trailingIcon: Icon( + Icons.add, + size: 20, + color: Theme.of(context).extension()!.iconsColor, + )); + } + + if (item is WalletAddressListItem) { + cell = Observer(builder: (_) { + final isCurrent = item.address == addressListViewModel.address.address && editable; + final backgroundColor = isCurrent + ? Theme.of(context).extension()!.currentTileBackgroundColor + : Theme.of(context).extension()!.tilesBackgroundColor; + final textColor = isCurrent + ? Theme.of(context).extension()!.currentTileTextColor + : Theme.of(context).extension()!.tilesTextColor; + + return AddressCell.fromItem( + item, + isCurrent: isCurrent, + hasBalance: addressListViewModel.isElectrumWallet, + backgroundColor: backgroundColor, + textColor: textColor, + onTap: (_) { + if (onSelect != null) { + onSelect!(item.address); + return; + } + addressListViewModel.setAddress(item); + }, + onEdit: editable + ? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item) + : null, + ); + }); + } + + return index != 0 + ? cell + : ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), topRight: Radius.circular(30)), + child: cell, + ); + }, + ), + ); + } +} diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index f79da8069..f229ea8ef 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -12,7 +12,7 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart'; -enum AddressTextFieldOption { paste, qrCode, addressBook } +enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses } class AddressTextField extends StatelessWidget { AddressTextField( @@ -34,6 +34,7 @@ class AddressTextField extends StatelessWidget { this.validator, this.onPushPasteButton, this.onPushAddressBookButton, + this.onPushAddressPickerButton, this.onSelectedContact, this.selectedCurrency}); @@ -56,6 +57,7 @@ class AddressTextField extends StatelessWidget { final FocusNode? focusNode; final Function(BuildContext context)? onPushPasteButton; final Function(BuildContext context)? onPushAddressBookButton; + final Function(BuildContext context)? onPushAddressPickerButton; final Function(ContactBase contact)? onSelectedContact; final CryptoCurrency? selectedCurrency; @@ -70,16 +72,13 @@ class AddressTextField extends StatelessWidget { enabled: isActive, controller: controller, focusNode: focusNode, - style: textStyle ?? TextStyle( fontSize: 16, color: Theme.of(context).extension()!.titleColor), decoration: InputDecoration( - suffixIcon: SizedBox( width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length), ), - hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor), hintText: placeholder ?? S.current.widgets_address, focusedBorder: isBorderExist @@ -104,90 +103,122 @@ class AddressTextField extends StatelessWidget { top: 2, right: 0, child: SizedBox( - width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length), + width: + (prefixIconWidth * options.length) + (spaceBetweenPrefixIcons * options.length), child: Row( mainAxisAlignment: responsiveLayoutUtil.shouldRenderMobileUI ? MainAxisAlignment.spaceBetween : MainAxisAlignment.end, children: [ - SizedBox(width: 5), if (this.options.contains(AddressTextFieldOption.paste)) ...[ + SizedBox(width: 5), Container( - width: prefixIconWidth, - height: prefixIconHeight, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).paste, - child: InkWell( - onTap: () async => _pasteAddress(context), - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: buttonColor ?? - Theme.of(context).dialogTheme.backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/paste_ios.png', - color: iconColor ?? - Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )), + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).paste, + child: InkWell( + onTap: () async => _pasteAddress(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/paste_ios.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ), ], if (this.options.contains(AddressTextFieldOption.qrCode) && DeviceInfo.instance.isMobile) ...[ - Container( - width: prefixIconWidth, - height: prefixIconHeight, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).scan_qr_code, - child: InkWell( - onTap: () async => _presentQRScanner(context), - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: buttonColor ?? - Theme.of(context).dialogTheme.backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/qr_code_icon.png', - color: iconColor ?? - Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )) - ] else SizedBox(width: 5), - if (this.options.contains(AddressTextFieldOption.addressBook)) ...[ Container( - width: prefixIconWidth, - height: prefixIconHeight, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).address_book, - child: InkWell( - onTap: () async => _presetAddressBookPicker(context), - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: buttonColor ?? - Theme.of(context).dialogTheme.backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/open_book.png', - color: iconColor ?? - Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )) - ] + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).scan_qr_code, + child: InkWell( + onTap: () async => _presentQRScanner(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/qr_code_icon.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ), + ], + if (this.options.contains(AddressTextFieldOption.addressBook)) ...[ + SizedBox(width: 5), + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).address_book, + child: InkWell( + onTap: () async => _presetAddressBookPicker(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/open_book.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ), + ], + if (this.options.contains(AddressTextFieldOption.walletAddresses)) ...[ + SizedBox(width: 5), + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).address_book, + child: InkWell( + onTap: () async => _presetWalletAddressPicker(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/open_book.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ) + ], ], ), )) @@ -197,7 +228,7 @@ class AddressTextField extends StatelessWidget { Future _presentQRScanner(BuildContext context) async { bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); + await PermissionHandler.checkPermission(Permission.camera, context); if (!isCameraPermissionGranted) return; final code = await presentQRScanner(); if (code.isEmpty) { @@ -224,6 +255,15 @@ class AddressTextField extends StatelessWidget { } } + Future _presetWalletAddressPicker(BuildContext context) async { + final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress); + + if (address is String) { + controller?.text = address; + onPushAddressPickerButton?.call(context); + } + } + Future _pasteAddress(BuildContext context) async { final clipboard = await Clipboard.getData('text/plain'); final address = clipboard?.text ?? ''; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index e98412dce..56a0e061b 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -182,7 +182,8 @@ abstract class DashboardViewModelBase with Store { final _accountTransactions = _wallet.transactionHistory.transactions.values .where((tx) => - wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id) + wow.wownero!.getTransactionInfoAccountId(tx) == + wow.wownero!.getCurrentAccount(wallet).id) .toList(); final sortedTransactions = [..._accountTransactions]; @@ -482,6 +483,27 @@ abstract class DashboardViewModelBase with Store { @computed bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano; + @computed + bool get hasSignMessages { + switch (wallet.type) { + case WalletType.monero: + case WalletType.litecoin: + case WalletType.bitcoin: + case WalletType.bitcoinCash: + case WalletType.ethereum: + case WalletType.polygon: + case WalletType.solana: + case WalletType.nano: + case WalletType.banano: + case WalletType.tron: + case WalletType.wownero: + return true; + case WalletType.haven: + case WalletType.none: + return false; + } + } + bool get showRepWarning { if (wallet.type != WalletType.nano) { return false; @@ -575,7 +597,8 @@ abstract class DashboardViewModelBase with Store { } if (wallet.type == WalletType.wownero) { - return wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id; + return wow.wownero!.getTransactionInfoAccountId(tx) == + wow.wownero!.getCurrentAccount(wallet).id; } return true; @@ -600,8 +623,8 @@ abstract class DashboardViewModelBase with Store { .getTransactionHistory(wallet) .transactions .values - .where( - (tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) + .where((tx) => + monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) .toList(); transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem( @@ -613,8 +636,9 @@ abstract class DashboardViewModelBase with Store { .getTransactionHistory(wallet) .transactions .values - .where( - (tx) => wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id) + .where((tx) => + wow.wownero!.getTransactionInfoAccountId(tx) == + wow.wownero!.getCurrentAccount(wallet).id) .toList(); transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem( diff --git a/lib/view_model/dashboard/sign_view_model.dart b/lib/view_model/dashboard/sign_view_model.dart new file mode 100644 index 000000000..5b1b4fc00 --- /dev/null +++ b/lib/view_model/dashboard/sign_view_model.dart @@ -0,0 +1,55 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:mobx/mobx.dart'; + +part 'sign_view_model.g.dart'; + +class SignViewModel = SignViewModelBase with _$SignViewModel; + +abstract class SignViewModelBase with Store { + SignViewModelBase(this.wallet) : state = InitialExecutionState(); + + final WalletBase wallet; + + @observable + ExecutionState state; + + @observable + bool isSigning = true; + + bool get signIncludesAddress => [ + WalletType.monero, + WalletType.bitcoin, + WalletType.bitcoinCash, + WalletType.litecoin, + WalletType.haven, + ].contains(wallet.type); + + @action + Future sign(String message, {String? address}) async { + state = IsExecutingState(); + try { + final signature = await wallet.signMessage(message, address: address); + state = ExecutedSuccessfullyState(payload: signature); + } catch (e) { + state = FailureState(e.toString()); + } + } + + @action + Future verify(String message, String signature, {String? address}) async { + state = IsExecutingState(); + try { + final sig = await wallet.verifyMessage(message, signature, address: address); + if (sig) { + state = ExecutedSuccessfullyState(); + } else { + state = FailureState(S.current.signature_invalid_error); + } + } catch (e) { + state = FailureState(e.toString()); + } + } +} diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 6c274bb7b..0bd936720 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -438,6 +439,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); } + if (wallet.type == WalletType.nano) { + addressList.add(WalletAddressListItem( + isPrimary: true, + name: null, + address: wallet.walletAddresses.address, + )); + } + if (wallet.type == WalletType.tron) { final primaryAddress = tron!.getAddress(wallet); diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index 5a9a1d093..e14934986 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -117,9 +117,7 @@ abstract class WalletCreationVMBase with Store { DerivationInfo? getDefaultDerivation() { switch (this.type) { case WalletType.nano: - return DerivationInfo( - derivationType: DerivationType.nano, - ); + return DerivationInfo(derivationType: DerivationType.nano); case WalletType.bitcoin: case WalletType.litecoin: return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first; @@ -131,9 +129,7 @@ abstract class WalletCreationVMBase with Store { DerivationInfo? getCommonRestoreDerivation() { switch (this.type) { case WalletType.nano: - return DerivationInfo( - derivationType: DerivationType.nano, - ); + return DerivationInfo(derivationType: DerivationType.nano); case WalletType.bitcoin: return DerivationInfo( derivationType: DerivationType.bip39, diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a2f346bae..07dc7f5af 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -103,7 +103,7 @@ dependencies: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v4 + ref: cake-update-v5 ledger_flutter: ^1.0.1 hashlib: 1.12.0 @@ -138,6 +138,10 @@ dependency_overrides: url: https://github.com/cake-tech/web3dart.git ref: cake flutter_secure_storage_platform_interface: 1.0.2 + bitcoin_base: + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v5 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index a73067383..f84f6102b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -370,6 +370,7 @@ "max_value": "الحد الأقصى: ${value} ${currency}", "memo": "مذكرة:", "message": "ﺔﻟﺎﺳﺭ", + "message_verified": "تم التحقق من الرسالة بنجاح", "methods": " ﻕﺮﻃُ", "min_amount": "الحد الأدنى: ${value}", "min_value": "الحد الأدنى: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "اظهار السييد / المفاتيح", "show_market_place": "إظهار السوق", "show_seed": "عرض السييد", + "sign_message": "تسجيل رسالة", "sign_up": "اشتراك", + "sign_verify_message": "توقيع أو التحقق من الرسالة", + "sign_verify_message_sub": "قم بتوقيع أو التحقق من رسالة باستخدام المفتاح الخاص بك", + "sign_verify_title": "تسجيل / تحقق", + "signature": "إمضاء", + "signature_invalid_error": "التوقيع غير صالح للرسالة المقدمة", "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.", "silent_payments": "مدفوعات صامتة", @@ -822,6 +829,7 @@ "value_type": "نوع القيمة", "variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة", "verification": "تَحَقّق", + "verify_message": "تحقق من الرسالة", "verify_with_2fa": "تحقق مع Cake 2FA", "version": "الإصدار ${currentVersion}", "view_all": "مشاهدة الكل", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index b60cbc55c..0b6b688bb 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -370,6 +370,7 @@ "max_value": "Макс: ${value} ${currency}", "memo": "Мемо:", "message": "Съобщение", + "message_verified": "Съобщението беше успешно проверено", "methods": "Методи", "min_amount": "Мин: ${value}", "min_value": "Мин: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Покажи seed/keys", "show_market_place": "Покажи пазар", "show_seed": "Покажи seed", + "sign_message": "Съобщение за подписване", "sign_up": "Регистрация", + "sign_verify_message": "Подпишете или проверете съобщението", + "sign_verify_message_sub": "Подпишете или проверете съобщение с помощта на вашия личен ключ", + "sign_verify_title": "Подпишете / проверете", + "signature": "Подпис", + "signature_invalid_error": "Подписът не е валиден за даденото съобщение", "signTransaction": "Подпишете транзакция", "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.", "silent_payments": "Мълчаливи плащания", @@ -822,6 +829,7 @@ "value_type": "Тип стойност", "variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса", "verification": "Потвърждаване", + "verify_message": "Проверете съобщението", "verify_with_2fa": "Проверете с Cake 2FA", "version": "Версия ${currentVersion}", "view_all": "Виж всички", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 537695dd9..ef225041d 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Zpráva", + "message_verified": "Zpráva byla úspěšně ověřena", "methods": "Metody", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Zobrazit seed/klíče", "show_market_place": "Zobrazit trh", "show_seed": "Zobrazit seed", + "sign_message": "Podepsat zprávu", "sign_up": "Registrovat se", + "sign_verify_message": "Podepište nebo ověřte zprávu", + "sign_verify_message_sub": "Podepište nebo ověřte zprávu pomocí soukromého klíče", + "sign_verify_title": "Podepsat / ověřit", + "signature": "Podpis", + "signature_invalid_error": "Podpis není platný pro danou zprávu", "signTransaction": "Podepsat transakci", "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.", "silent_payments": "Tiché platby", @@ -822,6 +829,7 @@ "value_type": "Typ hodnoty", "variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován", "verification": "Ověření", + "verify_message": "Ověřit zprávu", "verify_with_2fa": "Ověřte pomocí Cake 2FA", "version": "Verze ${currentVersion}", "view_all": "Zobrazit vše", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 052015367..393c3d928 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Nachricht", + "message_verified": "Die Nachricht wurde erfolgreich überprüft", "methods": "Methoden", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "Seed/Schlüssel anzeigen", "show_market_place": "Marktplatz anzeigen", "show_seed": "Seed zeigen", + "sign_message": "Nachricht unterschreiben", "sign_up": "Anmelden", + "sign_verify_message": "Nachricht unterschreiben oder überprüfen", + "sign_verify_message_sub": "Unterschreiben oder überprüfen Sie eine Nachricht mit Ihrem privaten Schlüssel", + "sign_verify_title": "Zeichen / überprüfen", + "signature": "Signatur", + "signature_invalid_error": "Die Signatur gilt nicht für die angegebene Nachricht", "signTransaction": "Transaktion unterzeichnen", "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", "silent_payments": "Stille Zahlungen", @@ -824,6 +831,7 @@ "value_type": "Werttyp", "variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt", "verification": "Verifizierung", + "verify_message": "Nachricht überprüfen", "verify_with_2fa": "Verifizieren Sie mit Cake 2FA", "version": "Version ${currentVersion}", "view_all": "Alle anzeigen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 0f0ebd470..4ac7dd8fd 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Message", + "message_verified": "The message was successfully verified", "methods": "Methods", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "Show seed/keys", "show_market_place": "Show Marketplace", "show_seed": "Show seed", + "sign_message": "Sign Message", "sign_up": "Sign Up", + "sign_verify_message": "Sign or verify message", + "sign_verify_message_sub": "Sign or verify a message using your private key", + "sign_verify_title": "Sign / Verify", + "signature": "Signature", + "signature_invalid_error": "The signature is not valid for the message given", "signTransaction": "Sign Transaction", "signup_for_card_accept_terms": "Sign up for the card and accept the terms.", "silent_payments": "Silent Payments", @@ -823,6 +830,7 @@ "value_type": "Value Type", "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges", "verification": "Verification", + "verify_message": "Verify Message", "verify_with_2fa": "Verify with Cake 2FA", "version": "Version ${currentVersion}", "view_all": "View all", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 1e6eeae59..02d17ac96 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memorándum:", "message": "Mensaje", + "message_verified": "El mensaje fue verificado con éxito", "methods": "Métodos", "min_amount": "Mínimo: ${value}", "min_value": "Min: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "Mostrar semilla/claves", "show_market_place": "Mostrar mercado", "show_seed": "Mostrar semilla", + "sign_message": "Mensaje de firma", "sign_up": "Registrarse", + "sign_verify_message": "Firmar o verificar el mensaje", + "sign_verify_message_sub": "Firmar o verificar un mensaje usando su clave privada", + "sign_verify_title": "Firmar / verificar", + "signature": "Firma", + "signature_invalid_error": "La firma no es válida para el mensaje dado", "signTransaction": "Firmar transacción", "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", "silent_payments": "Pagos silenciosos", @@ -823,6 +830,7 @@ "value_type": "Tipo de valor", "variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados", "verification": "Verificación", + "verify_message": "Mensaje de verificación", "verify_with_2fa": "Verificar con Cake 2FA", "version": "Versión ${currentVersion}", "view_all": "Ver todo", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index a64a69514..4bc5c9809 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Mémo :", "message": "Message", + "message_verified": "Le message a été vérifié avec succès", "methods": "Méthodes", "min_amount": "Min : ${value}", "min_value": "Min: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Visualiser la phrase secrète (seed) et les clefs", "show_market_place": "Afficher la place de marché", "show_seed": "Visualiser la phrase secrète (seed)", + "sign_message": "Signer le message", "sign_up": "S'inscrire", + "sign_verify_message": "Signer ou vérifier le message", + "sign_verify_message_sub": "Signez ou vérifiez un message en utilisant votre clé privée", + "sign_verify_title": "Signe / vérifier", + "signature": "Signature", + "signature_invalid_error": "La signature n'est pas valable pour le message donné", "signTransaction": "Signer une transaction", "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", "silent_payments": "Paiements silencieux", @@ -822,6 +829,7 @@ "value_type": "Type de valeur", "variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés", "verification": "Vérification", + "verify_message": "Vérifier le message", "verify_with_2fa": "Vérifier avec Cake 2FA", "version": "Version ${currentVersion}", "view_all": "Voir tout", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 219b3c939..025a33f6b 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Sako", + "message_verified": "An yi nasarar tabbatar da sakon", "methods": "Hanyoyin", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -663,7 +664,13 @@ "show_keys": "Nuna iri/maɓallai", "show_market_place": "Nuna dan kasuwa", "show_seed": "Nuna iri", + "sign_message": "Sa hannu", "sign_up": "Shiga", + "sign_verify_message": "Shiga ko Tabbatar da Saƙo", + "sign_verify_message_sub": "Shiga ko tabbatar da saƙo ta amfani da Maɓallinku na sirri", + "sign_verify_title": "Sa hannu / Tabbatar", + "signature": "Sa hannu", + "signature_invalid_error": "Sa hannu ba shi da inganci ga sakon da aka bayar", "signTransaction": "Sa hannu Ma'amala", "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.", "silent_payments": "Biya silent", @@ -824,6 +831,7 @@ "value_type": "Nau'in darajar", "variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar", "verification": "tabbatar", + "verify_message": "Tabbatar saƙon", "verify_with_2fa": "Tabbatar da Cake 2FA", "version": "Sigar ${currentVersion}", "view_all": "Duba duka", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b711939a4..346c420a9 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -370,6 +370,7 @@ "max_value": "मैक्स: ${value} ${currency}", "memo": "ज्ञापन:", "message": "संदेश", + "message_verified": "संदेश को सफलतापूर्वक सत्यापित किया गया था", "methods": "तरीकों", "min_amount": "न्यूनतम: ${value}", "min_value": "मिन: ${value} ${currency}", @@ -663,7 +664,13 @@ "show_keys": "बीज / कुंजियाँ दिखाएँ", "show_market_place": "बाज़ार दिखाएँ", "show_seed": "बीज दिखाओ", + "sign_message": "हस्ताक्षर संदेश", "sign_up": "साइन अप करें", + "sign_verify_message": "संदेश पर हस्ताक्षर या सत्यापित करें", + "sign_verify_message_sub": "अपनी निजी कुंजी का उपयोग करके किसी संदेश पर हस्ताक्षर या सत्यापित करें", + "sign_verify_title": "हस्ताक्षर / सत्यापित करें", + "signature": "हस्ताक्षर", + "signature_invalid_error": "हस्ताक्षर दिए गए संदेश के लिए मान्य नहीं है", "signTransaction": "लेन-देन पर हस्ताक्षर करें", "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", "silent_payments": "मूक भुगतान", @@ -824,6 +831,7 @@ "value_type": "मान प्रकार", "variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है", "verification": "सत्यापन", + "verify_message": "संदेश सत्यापित करें", "verify_with_2fa": "केक 2FA के साथ सत्यापित करें", "version": "संस्करण ${currentVersion}", "view_all": "सभी देखें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 401514735..6f5bc88a6 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -370,6 +370,7 @@ "max_value": "Maks.: ${value} ${currency}", "memo": "Memo:", "message": "Poruka", + "message_verified": "Poruka je uspješno provjerena", "methods": "Metode", "min_amount": "Minimalno: ${value}", "min_value": "Min.: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Prikaži pristupni izraz/ključ", "show_market_place": "Prikaži tržište", "show_seed": "Prikaži pristupni izraz", + "sign_message": "Poruka", "sign_up": "Prijavite se", + "sign_verify_message": "Potpisati ili provjeriti poruku", + "sign_verify_message_sub": "Potpišite ili provjerite poruku pomoću privatnog ključa", + "sign_verify_title": "Potpisati / provjeriti", + "signature": "Potpis", + "signature_invalid_error": "Potpis ne vrijedi za danu poruku", "signTransaction": "Potpišite transakciju", "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.", "silent_payments": "Tiha plaćanja", @@ -822,6 +829,7 @@ "value_type": "Tipa vrijednosti", "variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama", "verification": "Potvrda", + "verify_message": "Provjerite poruku", "verify_with_2fa": "Provjerite s Cake 2FA", "version": "Verzija ${currentVersion}", "view_all": "Prikaži sve", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b6210b5dd..2bc298aa7 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Pesan", + "message_verified": "Pesan itu berhasil diverifikasi", "methods": "Metode", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -664,7 +665,13 @@ "show_keys": "Tampilkan seed/kunci", "show_market_place": "Tampilkan Pasar", "show_seed": "Tampilkan seed", + "sign_message": "Pesan tanda", "sign_up": "Daftar", + "sign_verify_message": "Tanda tangan atau verifikasi pesan", + "sign_verify_message_sub": "Menandatangani atau memverifikasi pesan menggunakan kunci pribadi Anda", + "sign_verify_title": "Tanda / verifikasi", + "signature": "Tanda tangan", + "signature_invalid_error": "Tanda tangan tidak valid untuk pesan yang diberikan", "signTransaction": "Tandatangani Transaksi", "signup_for_card_accept_terms": "Daftar untuk kartu dan terima syarat dan ketentuan.", "silent_payments": "Pembayaran diam", @@ -825,6 +832,7 @@ "value_type": "Jenis Nilai", "variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih", "verification": "Verifikasi", + "verify_message": "Verifikasi pesan", "verify_with_2fa": "Verifikasi dengan Cake 2FA", "version": "Versi ${currentVersion}", "view_all": "Lihat Semua", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index fc4abf7c4..0548b0ad2 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -371,6 +371,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Messaggio", + "message_verified": "Il messaggio è stato verificato con successo", "methods": "Metodi", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -663,7 +664,13 @@ "show_keys": "Mostra seme/chiavi", "show_market_place": "Mostra mercato", "show_seed": "Mostra seme", + "sign_message": "Messaggio di firma", "sign_up": "Registrati", + "sign_verify_message": "Firmare o verificare il messaggio", + "sign_verify_message_sub": "Firma o verifica un messaggio utilizzando la chiave privata", + "sign_verify_title": "Firmare / verificare", + "signature": "Firma", + "signature_invalid_error": "La firma non è valida per il messaggio dato", "signTransaction": "Firma la transazione", "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.", "silent_payments": "Pagamenti silenziosi", @@ -824,6 +831,7 @@ "value_type": "Tipo di valore", "variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati", "verification": "Verifica", + "verify_message": "Verificare il messaggio", "verify_with_2fa": "Verifica con Cake 2FA", "version": "Versione ${currentVersion}", "view_all": "Visualizza tutto", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 98495fc8b..c53a6d001 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -371,6 +371,7 @@ "max_value": "マックス: ${value} ${currency}", "memo": "メモ:", "message": "メッセージ", + "message_verified": "メッセージは正常に検証されました", "methods": "メソッド", "min_amount": "最小: ${value}", "min_value": "分: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "シード/キーを表示する", "show_market_place": "マーケットプレイスを表示", "show_seed": "シードを表示", + "sign_message": "署名メッセージ", "sign_up": "サインアップ", + "sign_verify_message": "メッセージに署名または確認します", + "sign_verify_message_sub": "秘密鍵を使用してメッセージに署名または確認します", + "sign_verify_title": "署名 /検証", + "signature": "サイン", + "signature_invalid_error": "署名は、指定されたメッセージに対して無効です", "signTransaction": "トランザクションに署名する", "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。", "silent_payments": "サイレント支払い", @@ -823,6 +830,7 @@ "value_type": "値タイプ", "variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません", "verification": "検証", + "verify_message": "メッセージを確認します", "verify_with_2fa": "Cake 2FA で検証する", "version": "バージョン ${currentVersion}", "view_all": "すべて表示", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 60c52b21f..be6757d99 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -370,6 +370,7 @@ "max_value": "맥스: ${value} ${currency}", "memo": "메모:", "message": "메시지", + "message_verified": "메시지가 성공적으로 확인되었습니다", "methods": "행동 양식", "min_amount": "최소: ${value}", "min_value": "최소: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "시드 / 키 표시", "show_market_place": "마켓플레이스 표시", "show_seed": "종자 표시", + "sign_message": "서명 메시지", "sign_up": "가입", + "sign_verify_message": "메시지에 서명하거나 확인하십시오", + "sign_verify_message_sub": "개인 키를 사용하여 메시지에 서명하거나 확인하십시오", + "sign_verify_title": "서명 / 확인", + "signature": "서명", + "signature_invalid_error": "서명은 주어진 메시지에 유효하지 않습니다", "signTransaction": "거래 서명", "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.", "silent_payments": "조용한 지불", @@ -823,6 +830,7 @@ "value_type": "가치 유형", "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.", "verification": "검증", + "verify_message": "메시지를 확인하십시오", "verify_with_2fa": "케이크 2FA로 확인", "version": "버전 ${currentVersion}", "view_all": "모두 보기", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 42643be48..c53dffb6b 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -370,6 +370,7 @@ "max_value": "အများဆုံး- ${value} ${currency}", "memo": "မှတ်စုတို:", "message": "မက်ဆေ့ချ်", + "message_verified": "မက်ဆေ့ခ်ျကိုအောင်မြင်စွာအတည်ပြုခဲ့သည်", "methods": "နည်းလမ်းများ", "min_amount": "အနည်းဆုံး- ${value}", "min_value": "အနည်းဆုံး- ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "မျိုးစေ့ /သော့များကို ပြပါ။", "show_market_place": "စျေးကွက်ကိုပြသပါ။", "show_seed": "မျိုးစေ့ကိုပြပါ။", + "sign_message": "လက်မှတ်စာ", "sign_up": "ဆိုင်းအပ်", + "sign_verify_message": "မက်ဆေ့ခ်ျကိုလက်မှတ်ထိုးသို့မဟုတ်အတည်ပြုရန်", + "sign_verify_message_sub": "သင်၏ကိုယ်ပိုင်သော့ကို သုံး. မက်ဆေ့ခ်ျကိုလက်မှတ်ထိုးပါ", + "sign_verify_title": "လက်မှတ်ထိုး / အတည်ပြုရန်", + "signature": "လက်မှတ်", + "signature_invalid_error": "အဆိုပါလက်မှတ်ပေးထားသောမက်ဆေ့ခ်ျကိုများအတွက်မမှန်ကန်ပါ", "signTransaction": "ငွေလွှဲဝင်ပါ။", "signup_for_card_accept_terms": "ကတ်အတွက် စာရင်းသွင်းပြီး စည်းကမ်းချက်များကို လက်ခံပါ။", "silent_payments": "အသံတိတ်ငွေပေးချေမှု", @@ -822,6 +829,7 @@ "value_type": "Value အမျိုးအစား", "variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။", "verification": "စိစစ်ခြင်း။", + "verify_message": "မက်ဆေ့ခ်ျကိုအတည်ပြုရန်", "verify_with_2fa": "Cake 2FA ဖြင့် စစ်ဆေးပါ။", "version": "ဗားရှင်း ${currentVersion}", "view_all": "အားလုံးကိုကြည့်ရှုပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 0f6149182..2d55344f5 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Bericht", + "message_verified": "Het bericht is succesvol geverifieerd", "methods": "Methoden", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Toon zaad/sleutels", "show_market_place": "Toon Marktplaats", "show_seed": "Toon zaad", + "sign_message": "Aanmeldingsbericht", "sign_up": "Aanmelden", + "sign_verify_message": "Teken of verifieer bericht", + "sign_verify_message_sub": "Teken of verifieer een bericht met uw privésleutel", + "sign_verify_title": "Ondertekenen / verifiëren", + "signature": "Handtekening", + "signature_invalid_error": "De handtekening is niet geldig voor het gegeven bericht", "signTransaction": "Transactie ondertekenen", "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.", "silent_payments": "Stille betalingen", @@ -822,6 +829,7 @@ "value_type": "Waarde type", "variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen", "verification": "Verificatie", + "verify_message": "Verifieer bericht", "verify_with_2fa": "Controleer met Cake 2FA", "version": "Versie ${currentVersion}", "view_all": "Alles bekijken", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 48d6e38f6..833fc0308 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Notatka:", "message": "Wiadomość", + "message_verified": "Wiadomość została pomyślnie zweryfikowana", "methods": "Metody", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Pokaż seed/klucze", "show_market_place": "Pokaż rynek", "show_seed": "Pokaż frazy seed", + "sign_message": "Podpisuj wiadomość", "sign_up": "Zarejestruj się", + "sign_verify_message": "Podpisz lub zweryfikuj wiadomość", + "sign_verify_message_sub": "Podpisz lub zweryfikuj wiadomość za pomocą klucza prywatnego", + "sign_verify_title": "Podpisać / weryfikować", + "signature": "Podpis", + "signature_invalid_error": "Podpis nie jest ważny dla podanej wiadomości", "signTransaction": "Podpisz transakcję", "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.", "silent_payments": "Ciche płatności", @@ -822,6 +829,7 @@ "value_type": "Typ wartości", "variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach", "verification": "Weryfikacja", + "verify_message": "Sprawdź wiadomość", "verify_with_2fa": "Sprawdź za pomocą Cake 2FA", "version": "Wersja ${currentVersion}", "view_all": "Wyświetl wszystko", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 070f3f776..bd6d9b506 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -371,6 +371,7 @@ "max_value": "Máx: ${value} ${currency}", "memo": "Memorando:", "message": "Mensagem", + "message_verified": "A mensagem foi verificada com sucesso", "methods": "Métodos", "min_amount": "Mínimo: ${valor}", "min_value": "Mín: ${value} ${currency}", @@ -663,7 +664,13 @@ "show_keys": "Mostrar semente/chaves", "show_market_place": "Mostrar mercado", "show_seed": "Mostrar semente", + "sign_message": "Mensagem de assinar", "sign_up": "Inscrever-se", + "sign_verify_message": "Assinar ou verificar mensagem", + "sign_verify_message_sub": "Assine ou verifique uma mensagem usando sua chave privada", + "sign_verify_title": "Assinar / verificar", + "signature": "Assinatura", + "signature_invalid_error": "A assinatura não é válida para a mensagem dada", "signTransaction": "Assinar transação", "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.", "silent_payments": "Pagamentos silenciosos", @@ -824,6 +831,7 @@ "value_type": "Tipo de valor", "variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas", "verification": "Verificação", + "verify_message": "Verifique a mensagem", "verify_with_2fa": "Verificar com Cake 2FA", "version": "Versão ${currentVersion}", "view_all": "Ver todos", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 695877c77..d43572351 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -370,6 +370,7 @@ "max_value": "Макс: ${value} ${currency}", "memo": "Памятка:", "message": "Сообщение", + "message_verified": "Сообщение было успешно проверено", "methods": "Методы", "min_amount": "Минимум: ${value}", "min_value": "Мин: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "Показать мнемоническую фразу/ключи", "show_market_place": "Показать торговую площадку", "show_seed": "Показать мнемоническую фразу", + "sign_message": "Сообщение о знаке", "sign_up": "Зарегистрироваться", + "sign_verify_message": "Подписать или проверить сообщение", + "sign_verify_message_sub": "Подписать или проверить сообщение, используя свой закрытый ключ", + "sign_verify_title": "Знак / проверка", + "signature": "Подпись", + "signature_invalid_error": "Подпись недопустима для данного сообщения", "signTransaction": "Подписать транзакцию", "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.", "silent_payments": "Молчаливые платежи", @@ -823,6 +830,7 @@ "value_type": "Тип значения", "variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.", "verification": "Проверка", + "verify_message": "Проверьте сообщение", "verify_with_2fa": "Подтвердить с помощью Cake 2FA", "version": "Версия ${currentVersion}", "view_all": "Просмотреть все", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 5757eed0b..d948ba6d6 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -370,6 +370,7 @@ "max_value": "ขั้นสูง: ${value} ${currency}", "memo": "หมายเหตุ:", "message": "ข้อความ", + "message_verified": "ข้อความได้รับการตรวจสอบอย่างประสบความสำเร็จ", "methods": "วิธีการ", "min_amount": "จำนวนขั้นต่ำ: ${value}", "min_value": "ขั้นต่ำ: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "แสดงซีด/คีย์", "show_market_place": "แสดงตลาดกลาง", "show_seed": "แสดงซีด", + "sign_message": "ลงนามข้อความ", "sign_up": "สมัครสมาชิก", + "sign_verify_message": "ลงชื่อเข้าใช้หรือตรวจสอบข้อความ", + "sign_verify_message_sub": "ลงชื่อเข้าใช้หรือตรวจสอบข้อความโดยใช้คีย์ส่วนตัวของคุณ", + "sign_verify_title": "ลงชื่อ / ตรวจสอบ", + "signature": "ลายเซ็น", + "signature_invalid_error": "ลายเซ็นไม่ถูกต้องสำหรับข้อความที่ให้ไว้", "signTransaction": "ลงนามในการทำธุรกรรม", "signup_for_card_accept_terms": "ลงทะเบียนสำหรับบัตรและยอมรับเงื่อนไข", "silent_payments": "การชำระเงินเงียบ", @@ -822,6 +829,7 @@ "value_type": "ประเภทค่า", "variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก", "verification": "การตรวจสอบ", + "verify_message": "ยืนยันข้อความ", "verify_with_2fa": "ตรวจสอบกับ Cake 2FA", "version": "เวอร์ชัน ${currentVersion}", "view_all": "ดูทั้งหมด", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 7fc83afd8..a0ab030c8 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -370,6 +370,7 @@ "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Mensahe", + "message_verified": "Ang mensahe ay matagumpay na na -verify", "methods": "Mga Paraan", "min_amount": "Min: ${value}", "min_value": "Min: ${value} ${currency}", @@ -660,6 +661,12 @@ "show_details": "Ipakita ang mga detalye", "show_keys": "Ipakita ang mga seed/key", "show_market_place": "Ipakita ang Marketplace", + "sign_message": "Mag -sign Message", + "sign_verify_message": "Mag -sign o i -verify ang mensahe", + "sign_verify_message_sub": "Mag -sign o i -verify ang isang mensahe gamit ang iyong pribadong key", + "sign_verify_title": "Mag -sign / Mag -verify", + "signature": "Lagda", + "signature_invalid_error": "Ang lagda ay hindi wasto para sa ibinigay na mensahe", "show_seed": "Ipakita ang seed", "sign_up": "Mag-sign Up", "signTransaction": "Mag-sign ang Transaksyon", @@ -820,6 +827,7 @@ "use_testnet": "Gumamit ng testnet", "value": "Halaga", "value_type": "Uri ng halaga", + "verify_message": "I -verify ang mensahe", "variable_pair_not_supported": "Ang variable na pares na ito ay hindi suportado sa mga napiling exchange", "verification": "Pag-verify", "verify_with_2fa": "Mag-verify sa Cake 2FA", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 4d1aa43e4..e7cdc5b12 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -370,6 +370,7 @@ "max_value": "En fazla: ${value} ${currency}", "memo": "Memo:", "message": "İleti", + "message_verified": "Mesaj başarıyla doğrulandı", "methods": "Yöntemler", "min_amount": "Min: ${value}", "min_value": "En az: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "Tohumları/anahtarları göster", "show_market_place": "Pazar Yerini Göster", "show_seed": "Tohumları göster", + "sign_message": "İşaret mesajı", "sign_up": "Kaydol", + "sign_verify_message": "Mesajı işaretleyin veya doğrulayın", + "sign_verify_message_sub": "Özel anahtarınızı kullanarak bir mesajı imzalayın veya doğrulayın", + "sign_verify_title": "İşaretle / Doğrula", + "signature": "İmza", + "signature_invalid_error": "İmza verilen mesaj için geçerli değil", "signTransaction": "İşlem İmzala", "signup_for_card_accept_terms": "Kart için kaydol ve koşulları kabul et.", "silent_payments": "Sessiz ödemeler", @@ -822,6 +829,7 @@ "value_type": "Değer türü", "variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte", "verification": "Doğrulama", + "verify_message": "Mesajı Doğrula", "verify_with_2fa": "Cake 2FA ile Doğrulayın", "version": "Sürüm ${currentVersion}", "view_all": "Hepsini göster", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 22edec0d5..91b5b5266 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -370,6 +370,7 @@ "max_value": "Макс: ${value} ${currency}", "memo": "Пам’ятка:", "message": "повідомлення", + "message_verified": "Повідомлення було успішно перевірено", "methods": "методи", "min_amount": "Мінімум: ${value}", "min_value": "Мін: ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "Показати мнемонічну фразу/ключі", "show_market_place": "Відображати маркетплейс", "show_seed": "Показати мнемонічну фразу", + "sign_message": "Підпишіть повідомлення", "sign_up": "Зареєструватися", + "sign_verify_message": "Підпишіть або перевірити повідомлення", + "sign_verify_message_sub": "Підпишіть або перевірте повідомлення за допомогою вашого приватного ключа", + "sign_verify_title": "Знак / Перевірка", + "signature": "Підпис", + "signature_invalid_error": "Підпис не є дійсним для наведеного повідомлення", "signTransaction": "Підписати транзакцію", "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.", "silent_payments": "Мовчазні платежі", @@ -823,6 +830,7 @@ "value_type": "Тип значення", "variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами", "verification": "Перевірка", + "verify_message": "Перевірте повідомлення", "verify_with_2fa": "Перевірте за допомогою Cake 2FA", "version": "Версія ${currentVersion}", "view_all": "Переглянути все", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 590f344d8..e54a0db34 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -370,6 +370,7 @@ "max_value": "زیادہ سے زیادہ: ${value} ${currency}", "memo": "میمو:", "message": "ﻡﺎﻐﯿﭘ", + "message_verified": "پیغام کی کامیابی کے ساتھ تصدیق کی گئی", "methods": "ﮯﻘﯾﺮﻃ", "min_amount": "کم سے کم: ${value}", "min_value": "کم سے کم: ${value} ${currency}", @@ -663,7 +664,13 @@ "show_keys": "بیج / چابیاں دکھائیں۔", "show_market_place": "بازار دکھائیں۔", "show_seed": "بیج دکھائیں۔", + "sign_message": "سائن پیغام", "sign_up": "سائن اپ", + "sign_verify_message": "پیغام پر دستخط کریں یا تصدیق کریں", + "sign_verify_message_sub": "اپنی نجی کلید کا استعمال کرتے ہوئے کسی پیغام پر دستخط کریں یا اس کی تصدیق کریں", + "sign_verify_title": "سائن / تصدیق کریں", + "signature": "دستخط", + "signature_invalid_error": "دستخط دیئے گئے پیغام کے لئے درست نہیں ہے", "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "signup_for_card_accept_terms": "کارڈ کے لیے سائن اپ کریں اور شرائط کو قبول کریں۔", "silent_payments": "خاموش ادائیگی", @@ -824,6 +831,7 @@ "value_type": "قدر کی قسم", "variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔", "verification": "تصدیق", + "verify_message": "پیغام کی تصدیق کریں", "verify_with_2fa": "کیک 2FA سے تصدیق کریں۔", "version": "ورژن ${currentVersion}", "view_all": "سب دیکھیں", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index e5cf3d3f9..8f47d8543 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -371,6 +371,7 @@ "max_value": "kò gbọ́dọ̀ tóbi ju ${value} ${currency}", "memo": "Àkọsílẹ̀:", "message": "Ifiranṣẹ", + "message_verified": "Ifiranṣẹ naa ni aṣeyọri ni ifijišẹ", "methods": "Awọn ọna", "min_amount": "kò kéré ju: ${value}", "min_value": "kò gbọ́dọ̀ kéré ju ${value} ${currency}", @@ -662,7 +663,13 @@ "show_keys": "Wo hóró / àwọn kọ́kọ́rọ́", "show_market_place": "Wa Sopọ Pataki", "show_seed": "Wo hóró", + "sign_message": "Ifiranṣẹ Ami", "sign_up": "Forúkọ sílẹ̀", + "sign_verify_message": "Ami tabi ṣayẹwo ifiranṣẹ", + "sign_verify_message_sub": "Wọle tabi ṣayẹwo ifiranṣẹ kan nipa lilo bọtini ikọkọ rẹ", + "sign_verify_title": "Ami / Daju", + "signature": "Ibọwọlu", + "signature_invalid_error": "Ibuwọlu ko wulo fun ifiranṣẹ ti a fun", "signTransaction": "Wole Idunadura", "signup_for_card_accept_terms": "Ẹ f'orúkọ sílẹ̀ láti gba káàdì àti àjọrò.", "silent_payments": "Awọn sisanwo ipalọlọ", @@ -823,6 +830,7 @@ "value_type": "Iru iye", "variable_pair_not_supported": "A kì í ṣe k'á fi àwọn ilé pàṣípààrọ̀ yìí ṣe pàṣípààrọ̀ irú owó méji yìí", "verification": "Ìjẹ́rìísí", + "verify_message": "Daju ifiranṣẹ", "verify_with_2fa": "Ṣeẹda pẹlu Cake 2FA", "version": "Àtúnse ${currentVersion}", "view_all": "Wo gbogbo nǹkan kan", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 25024d0ed..c864a529b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -370,6 +370,7 @@ "max_value": "最大: ${value} ${currency}", "memo": "备忘录:", "message": "信息", + "message_verified": "该消息已成功验证", "methods": "方法", "min_amount": "最小值: ${value}", "min_value": "最小: ${value} ${currency}", @@ -661,7 +662,13 @@ "show_keys": "显示种子/密钥", "show_market_place": "显示市场", "show_seed": "显示种子", + "sign_message": "标志消息", "sign_up": "注册", + "sign_verify_message": "签名或验证消息", + "sign_verify_message_sub": "使用您的私钥签名或验证消息", + "sign_verify_title": "签名 /验证", + "signature": "签名", + "signature_invalid_error": "签名对于给出的消息无效", "signTransaction": "签署交易", "signup_for_card_accept_terms": "注册卡并接受条款。", "silent_payments": "无声付款", @@ -822,6 +829,7 @@ "value_type": "值类型", "variable_pair_not_supported": "所选交易所不支持此变量对", "verification": "验证", + "verify_message": "验证消息", "verify_with_2fa": "用 Cake 2FA 验证", "version": "版本 ${currentVersion}", "view_all": "查看全部",