// /* // * This file is part of Stack Wallet. // * // * Copyright (c) 2023 Cypher Stack // * All Rights Reserved. // * The code is distributed under GPLv3 license, see LICENSE file for details. // * Generated by Cypher Stack on 2023-05-26 // * // */ // // import 'dart:convert'; // import 'dart:math'; // import 'dart:typed_data'; // // import 'package:bip32/bip32.dart' as bip32; // import 'package:bip47/bip47.dart'; // import 'package:bip47/src/util.dart'; // import 'package:bitcoindart/bitcoindart.dart' as btc_dart; // import 'package:bitcoindart/src/utils/constants/op.dart' as op; // import 'package:bitcoindart/src/utils/script.dart' as bscript; // import 'package:isar/isar.dart'; // import 'package:pointycastle/digests/sha256.dart'; // import 'package:stackwallet/db/isar/main_db.dart'; // import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; // import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart'; // import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart'; // import 'package:stackwallet/models/isar/models/isar_models.dart'; // import 'package:stackwallet/models/signing_data.dart'; // import 'package:stackwallet/utilities/amount/amount.dart'; // import 'package:stackwallet/utilities/bip32_utils.dart'; // import 'package:stackwallet/utilities/bip47_utils.dart'; // import 'package:stackwallet/utilities/enums/coin_enum.dart'; // import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; // import 'package:stackwallet/utilities/format.dart'; // import 'package:stackwallet/utilities/logger.dart'; // import 'package:stackwallet/wallets/models/tx_data.dart'; // import 'package:tuple/tuple.dart'; // // const String kPCodeKeyPrefix = "pCode_key_"; // // String _basePaynymDerivePath({required bool testnet}) => // "m/47'/${testnet ? "1" : "0"}'/0'"; // String _notificationDerivationPath({required bool testnet}) => // "${_basePaynymDerivePath(testnet: testnet)}/0"; // // String _receivingPaynymAddressDerivationPath( // int index, { // required bool testnet, // }) => // "${_basePaynymDerivePath(testnet: testnet)}/$index/0"; // String _sendPaynymAddressDerivationPath( // int index, { // required bool testnet, // }) => // "${_basePaynymDerivePath(testnet: testnet)}/0/$index"; // // mixin PaynymWalletInterface { // // passed in wallet data // late final String _walletId; // late final String _walletName; // late final btc_dart.NetworkType _network; // late final Coin _coin; // late final MainDB _db; // late final ElectrumXClient _electrumXClient; // late final SecureStorageInterface _secureStorage; // late final int _dustLimit; // late final int _dustLimitP2PKH; // late final int _minConfirms; // // // passed in wallet functions // late final Future Function() _getMnemonicString; // late final Future Function() _getMnemonicPassphrase; // late final Future Function() _getChainHeight; // late final Future Function() _getCurrentChangeAddress; // late final int Function({ // required int vSize, // required int feeRatePerKB, // }) _estimateTxFee; // late final Future> Function({ // required String address, // required Amount amount, // Map? args, // }) _prepareSend; // late final Future Function({ // required String address, // }) _getTxCount; // late final Future> Function( // List utxosToUse, // ) _fetchBuildTxData; // late final Future Function() _refresh; // late final Future Function() _checkChangeAddressForTransactions; // // // initializer // void initPaynymWalletInterface({ // required String walletId, // required String walletName, // required btc_dart.NetworkType network, // required Coin coin, // required MainDB db, // required ElectrumXClient electrumXClient, // required SecureStorageInterface secureStorage, // required int dustLimit, // required int dustLimitP2PKH, // required int minConfirms, // required Future Function() getMnemonicString, // required Future Function() getMnemonicPassphrase, // required Future Function() getChainHeight, // required Future Function() getCurrentChangeAddress, // required int Function({ // required int vSize, // required int feeRatePerKB, // }) estimateTxFee, // required Future> Function({ // required String address, // required Amount amount, // Map? args, // }) prepareSend, // required Future Function({ // required String address, // }) getTxCount, // required Future> Function( // List utxosToUse, // ) fetchBuildTxData, // required Future Function() refresh, // required Future Function() checkChangeAddressForTransactions, // }) { // _walletId = walletId; // _walletName = walletName; // _network = network; // _coin = coin; // _db = db; // _electrumXClient = electrumXClient; // _secureStorage = secureStorage; // _dustLimit = dustLimit; // _dustLimitP2PKH = dustLimitP2PKH; // _minConfirms = minConfirms; // _getMnemonicString = getMnemonicString; // _getMnemonicPassphrase = getMnemonicPassphrase; // _getChainHeight = getChainHeight; // _getCurrentChangeAddress = getCurrentChangeAddress; // _estimateTxFee = estimateTxFee; // _prepareSend = prepareSend; // _getTxCount = getTxCount; // _fetchBuildTxData = fetchBuildTxData; // _refresh = refresh; // _checkChangeAddressForTransactions = checkChangeAddressForTransactions; // } // // // convenience getter // btc_dart.NetworkType get networkType => _network; // // Future getBip47BaseNode() async { // final root = await _getRootNode(); // final node = root.derivePath( // _basePaynymDerivePath( // testnet: _coin.isTestNet, // ), // ); // return node; // } // // Future getPrivateKeyForPaynymReceivingAddress({ // required String paymentCodeString, // required int index, // }) async { // final bip47base = await getBip47BaseNode(); // // final paymentAddress = PaymentAddress( // bip32Node: bip47base.derive(index), // paymentCode: PaymentCode.fromPaymentCode( // paymentCodeString, // networkType: networkType, // ), // networkType: networkType, // index: 0, // ); // // final pair = paymentAddress.getReceiveAddressKeyPair(); // // return pair.privateKey!; // } // // Future
currentReceivingPaynymAddress({ // required PaymentCode sender, // required bool isSegwit, // }) async { // final keys = await lookupKey(sender.toString()); // // final address = await _db // .getAddresses(_walletId) // .filter() // .subTypeEqualTo(AddressSubType.paynymReceive) // .and() // .group((q) { // if (isSegwit) { // return q // .typeEqualTo(AddressType.p2sh) // .or() // .typeEqualTo(AddressType.p2wpkh); // } else { // return q.typeEqualTo(AddressType.p2pkh); // } // }) // .and() // .anyOf(keys, (q, String e) => q.otherDataEqualTo(e)) // .sortByDerivationIndexDesc() // .findFirst(); // // if (address == null) { // final generatedAddress = await _generatePaynymReceivingAddress( // sender: sender, // index: 0, // generateSegwitAddress: isSegwit, // ); // // final existing = await _db // .getAddresses(_walletId) // .filter() // .valueEqualTo(generatedAddress.value) // .findFirst(); // // if (existing == null) { // // Add that new address // await _db.putAddress(generatedAddress); // } else { // // we need to update the address // await _db.updateAddress(existing, generatedAddress); // } // // return currentReceivingPaynymAddress( // isSegwit: isSegwit, // sender: sender, // ); // } else { // return address; // } // } // // Future
_generatePaynymReceivingAddress({ // required PaymentCode sender, // required int index, // required bool generateSegwitAddress, // }) async { // final root = await _getRootNode(); // final node = root.derivePath( // _basePaynymDerivePath( // testnet: _coin.isTestNet, // ), // ); // // final paymentAddress = PaymentAddress( // bip32Node: node.derive(index), // paymentCode: sender, // networkType: networkType, // index: 0, // ); // // final addressString = generateSegwitAddress // ? paymentAddress.getReceiveAddressP2WPKH() // : paymentAddress.getReceiveAddressP2PKH(); // // final address = Address( // walletId: _walletId, // value: addressString, // publicKey: [], // derivationIndex: index, // derivationPath: DerivationPath() // ..value = _receivingPaynymAddressDerivationPath( // index, // testnet: _coin.isTestNet, // ), // type: generateSegwitAddress ? AddressType.p2wpkh : AddressType.p2pkh, // subType: AddressSubType.paynymReceive, // otherData: await storeCode(sender.toString()), // ); // // return address; // } // // Future
_generatePaynymSendAddress({ // required PaymentCode other, // required int index, // required bool generateSegwitAddress, // bip32.BIP32? mySendBip32Node, // }) async { // final node = mySendBip32Node ?? await deriveNotificationBip32Node(); // // final paymentAddress = PaymentAddress( // bip32Node: node, // paymentCode: other, // networkType: networkType, // index: index, // ); // // final addressString = generateSegwitAddress // ? paymentAddress.getSendAddressP2WPKH() // : paymentAddress.getSendAddressP2PKH(); // // final address = Address( // walletId: _walletId, // value: addressString, // publicKey: [], // derivationIndex: index, // derivationPath: DerivationPath() // ..value = _sendPaynymAddressDerivationPath( // index, // testnet: _coin.isTestNet, // ), // type: AddressType.nonWallet, // subType: AddressSubType.paynymSend, // otherData: await storeCode(other.toString()), // ); // // return address; // } // // Future checkCurrentPaynymReceivingAddressForTransactions({ // required PaymentCode sender, // required bool isSegwit, // }) async { // final address = await currentReceivingPaynymAddress( // sender: sender, // isSegwit: isSegwit, // ); // // final txCount = await _getTxCount(address: address.value); // if (txCount > 0) { // // generate next address and add to db // final nextAddress = await _generatePaynymReceivingAddress( // sender: sender, // index: address.derivationIndex + 1, // generateSegwitAddress: isSegwit, // ); // // final existing = await _db // .getAddresses(_walletId) // .filter() // .valueEqualTo(nextAddress.value) // .findFirst(); // // if (existing == null) { // // Add that new address // await _db.putAddress(nextAddress); // } else { // // we need to update the address // await _db.updateAddress(existing, nextAddress); // } // // keep checking until address with no tx history is set as current // await checkCurrentPaynymReceivingAddressForTransactions( // sender: sender, // isSegwit: isSegwit, // ); // } // } // // Future checkAllCurrentReceivingPaynymAddressesForTransactions() async { // final codes = await getAllPaymentCodesFromNotificationTransactions(); // final List> futures = []; // for (final code in codes) { // futures.add(checkCurrentPaynymReceivingAddressForTransactions( // sender: code, // isSegwit: true, // )); // futures.add(checkCurrentPaynymReceivingAddressForTransactions( // sender: code, // isSegwit: false, // )); // } // await Future.wait(futures); // } // // // generate bip32 payment code root // Future _getRootNode() async { // return _cachedRootNode ??= await Bip32Utils.getBip32Root( // (await _getMnemonicString())!, // (await _getMnemonicPassphrase())!, // _network, // ); // } // // bip32.BIP32? _cachedRootNode; // // Future deriveNotificationBip32Node() async { // final root = await _getRootNode(); // final node = root // .derivePath( // _basePaynymDerivePath( // testnet: _coin.isTestNet, // ), // ) // .derive(0); // return node; // } // // /// fetch or generate this wallet's bip47 payment code // Future getPaymentCode({ // required bool isSegwit, // }) async { // final node = await _getRootNode(); // // final paymentCode = PaymentCode.fromBip32Node( // node.derivePath(_basePaynymDerivePath(testnet: _coin.isTestNet)), // networkType: networkType, // shouldSetSegwitBit: isSegwit, // ); // // return paymentCode; // } // // Future signWithNotificationKey(Uint8List data) async { // final myPrivateKeyNode = await deriveNotificationBip32Node(); // final pair = btc_dart.ECPair.fromPrivateKey(myPrivateKeyNode.privateKey!, // network: _network); // final signed = pair.sign(SHA256Digest().process(data)); // return signed; // } // // Future signStringWithNotificationKey(String data) async { // final bytes = // await signWithNotificationKey(Uint8List.fromList(utf8.encode(data))); // return Format.uint8listToString(bytes); // } // // Future> preparePaymentCodeSend({ // required PaymentCode paymentCode, // required bool isSegwit, // required Amount amount, // Map? args, // }) async { // if (!(await hasConnected(paymentCode.toString()))) { // throw PaynymSendException( // "No notification transaction sent to $paymentCode"); // } else { // final myPrivateKeyNode = await deriveNotificationBip32Node(); // final sendToAddress = await nextUnusedSendAddressFrom( // pCode: paymentCode, // privateKeyNode: myPrivateKeyNode, // isSegwit: isSegwit, // ); // // return _prepareSend( // address: sendToAddress.value, // amount: amount, // args: args, // ); // } // } // // /// get the next unused address to send to given the receiver's payment code // /// and your own private key // Future
nextUnusedSendAddressFrom({ // required PaymentCode pCode, // required bool isSegwit, // required bip32.BIP32 privateKeyNode, // int startIndex = 0, // }) async { // // https://en.bitcoin.it/wiki/BIP_0047#Path_levels // const maxCount = 2147483647; // // for (int i = startIndex; i < maxCount; i++) { // final keys = await lookupKey(pCode.toString()); // final address = await _db // .getAddresses(_walletId) // .filter() // .subTypeEqualTo(AddressSubType.paynymSend) // .and() // .anyOf(keys, (q, String e) => q.otherDataEqualTo(e)) // .and() // .derivationIndexEqualTo(i) // .findFirst(); // // if (address != null) { // final count = await _getTxCount(address: address.value); // // return address if unused, otherwise continue to next index // if (count == 0) { // return address; // } // } else { // final address = await _generatePaynymSendAddress( // other: pCode, // index: i, // generateSegwitAddress: isSegwit, // mySendBip32Node: privateKeyNode, // ); // // final storedAddress = await _db.getAddress(_walletId, address.value); // if (storedAddress == null) { // await _db.putAddress(address); // } else { // await _db.updateAddress(storedAddress, address); // } // final count = await _getTxCount(address: address.value); // // return address if unused, otherwise continue to next index // if (count == 0) { // return address; // } // } // } // // throw PaynymSendException("Exhausted unused send addresses!"); // } // // Future prepareNotificationTx({ // required int selectedTxFeeRate, // required String targetPaymentCodeString, // int additionalOutputs = 0, // List? utxos, // }) async { // try { // final amountToSend = _dustLimitP2PKH; // final List availableOutputs = // utxos ?? await _db.getUTXOs(_walletId).findAll(); // final List spendableOutputs = []; // int spendableSatoshiValue = 0; // // // Build list of spendable outputs and totaling their satoshi amount // for (var i = 0; i < availableOutputs.length; i++) { // if (availableOutputs[i].isBlocked == false && // availableOutputs[i] // .isConfirmed(await _getChainHeight(), _minConfirms) == // true) { // spendableOutputs.add(availableOutputs[i]); // spendableSatoshiValue += availableOutputs[i].value; // } // } // // if (spendableSatoshiValue < amountToSend) { // // insufficient balance // throw InsufficientBalanceException( // "Spendable balance is less than the minimum required for a notification transaction."); // } else if (spendableSatoshiValue == amountToSend) { // // insufficient balance due to missing amount to cover fee // throw InsufficientBalanceException( // "Remaining balance does not cover the network fee."); // } // // // sort spendable by age (oldest first) // spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); // // int satoshisBeingUsed = 0; // int outputsBeingUsed = 0; // List utxoObjectsToUse = []; // // for (int i = 0; // satoshisBeingUsed < amountToSend && i < spendableOutputs.length; // i++) { // utxoObjectsToUse.add(spendableOutputs[i]); // satoshisBeingUsed += spendableOutputs[i].value; // outputsBeingUsed += 1; // } // // // add additional outputs if required // for (int i = 0; // i < additionalOutputs && outputsBeingUsed < spendableOutputs.length; // i++) { // utxoObjectsToUse.add(spendableOutputs[outputsBeingUsed]); // satoshisBeingUsed += spendableOutputs[outputsBeingUsed].value; // outputsBeingUsed += 1; // } // // // gather required signing data // final utxoSigningData = await _fetchBuildTxData(utxoObjectsToUse); // // final int vSizeForNoChange = (await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxoSigningData: utxoSigningData, // change: 0, // // override amount to get around absurd fees error // overrideAmountForTesting: satoshisBeingUsed, // )) // .item2; // // final int vSizeForWithChange = (await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxoSigningData: utxoSigningData, // change: satoshisBeingUsed - amountToSend, // )) // .item2; // // // Assume 2 outputs, for recipient and payment code script // int feeForNoChange = _estimateTxFee( // vSize: vSizeForNoChange, // feeRatePerKB: selectedTxFeeRate, // ); // // // Assume 3 outputs, for recipient, payment code script, and change // int feeForWithChange = _estimateTxFee( // vSize: vSizeForWithChange, // feeRatePerKB: selectedTxFeeRate, // ); // // if (_coin == Coin.dogecoin || _coin == Coin.dogecoinTestNet) { // if (feeForNoChange < vSizeForNoChange * 1000) { // feeForNoChange = vSizeForNoChange * 1000; // } // if (feeForWithChange < vSizeForWithChange * 1000) { // feeForWithChange = vSizeForWithChange * 1000; // } // } // // if (satoshisBeingUsed - amountToSend > feeForNoChange + _dustLimitP2PKH) { // // try to add change output due to "left over" amount being greater than // // the estimated fee + the dust limit // int changeAmount = satoshisBeingUsed - amountToSend - feeForWithChange; // // // check estimates are correct and build notification tx // if (changeAmount >= _dustLimitP2PKH && // satoshisBeingUsed - amountToSend - changeAmount == // feeForWithChange) { // var txn = await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxoSigningData: utxoSigningData, // change: changeAmount, // ); // // int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount; // // // make sure minimum fee is accurate if that is being used // if (txn.item2 - feeBeingPaid == 1) { // changeAmount -= 1; // feeBeingPaid += 1; // txn = await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxoSigningData: utxoSigningData, // change: changeAmount, // ); // } // // final txData = TxData( // raw: txn.item1, // recipients: [ // ( // address: targetPaymentCodeString, // amount: amountToSend.toAmountAsRaw( // fractionDigits: _coin.decimals, // ), // ) // ], // fee: feeBeingPaid.toAmountAsRaw( // fractionDigits: _coin.decimals, // ), // vSize: txn.item2, // utxos: utxoSigningData.map((e) => e.utxo).toSet(), // note: "PayNym connect"); // // return txData; // } else { // // something broke during fee estimation or the change amount is smaller // // than the dust limit. Try without change // final txn = await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxoSigningData: utxoSigningData, // change: 0, // ); // // int feeBeingPaid = satoshisBeingUsed - amountToSend; // // final txData = TxData( // raw: txn.item1, // recipients: [ // ( // address: targetPaymentCodeString, // amount: amountToSend.toAmountAsRaw( // fractionDigits: _coin.decimals, // ), // ) // ], // fee: feeBeingPaid.toAmountAsRaw( // fractionDigits: _coin.decimals, // ), // vSize: txn.item2, // utxos: utxoSigningData.map((e) => e.utxo).toSet(), // note: "PayNym connect"); // // return txData; // } // } else if (satoshisBeingUsed - amountToSend >= feeForNoChange) { // // since we already checked if we need to add a change output we can just // // build without change here // final txn = await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxoSigningData: utxoSigningData, // change: 0, // ); // // int feeBeingPaid = satoshisBeingUsed - amountToSend; // // final txData = TxData( // raw: txn.item1, // recipients: [ // ( // address: targetPaymentCodeString, // amount: amountToSend.toAmountAsRaw( // fractionDigits: _coin.decimals, // ), // ) // ], // fee: feeBeingPaid.toAmountAsRaw( // fractionDigits: _coin.decimals, // ), // vSize: txn.item2, // utxos: utxoSigningData.map((e) => e.utxo).toSet(), // note: "PayNym connect"); // // return txData; // } else { // // if we get here we do not have enough funds to cover the tx total so we // // check if we have any more available outputs and try again // if (spendableOutputs.length > outputsBeingUsed) { // return prepareNotificationTx( // selectedTxFeeRate: selectedTxFeeRate, // targetPaymentCodeString: targetPaymentCodeString, // additionalOutputs: additionalOutputs + 1, // ); // } else { // throw InsufficientBalanceException( // "Remaining balance does not cover the network fee."); // } // } // } catch (e) { // rethrow; // } // } // // // return tuple with string value equal to the raw tx hex and the int value // // equal to its vSize // Future> _createNotificationTx({ // required String targetPaymentCodeString, // required List utxoSigningData, // required int change, // int? overrideAmountForTesting, // }) async { // try { // final targetPaymentCode = PaymentCode.fromPaymentCode( // targetPaymentCodeString, // networkType: _network, // ); // final myCode = await getPaymentCode(isSegwit: false); // // final utxo = utxoSigningData.first.utxo; // final txPoint = utxo.txid.fromHex.reversed.toList(); // final txPointIndex = utxo.vout; // // final rev = Uint8List(txPoint.length + 4); // Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); // final buffer = rev.buffer.asByteData(); // buffer.setUint32(txPoint.length, txPointIndex, Endian.little); // // final myKeyPair = utxoSigningData.first.keyPair!; // // final S = SecretPoint( // myKeyPair.privateKey!, // targetPaymentCode.notificationPublicKey(), // ); // // final blindingMask = PaymentCode.getMask(S.ecdhSecret(), rev); // // final blindedPaymentCode = PaymentCode.blind( // payload: myCode.getPayload(), // mask: blindingMask, // unBlind: false, // ); // // final opReturnScript = bscript.compile([ // (op.OPS["OP_RETURN"] as int), // blindedPaymentCode, // ]); // // // build a notification tx // final txb = btc_dart.TransactionBuilder(network: _network); // txb.setVersion(1); // // txb.addInput( // utxo.txid, // txPointIndex, // null, // utxoSigningData.first.output!, // ); // // // add rest of possible inputs // for (var i = 1; i < utxoSigningData.length; i++) { // final utxo = utxoSigningData[i].utxo; // txb.addInput( // utxo.txid, // utxo.vout, // null, // utxoSigningData[i].output!, // ); // } // final String notificationAddress = // targetPaymentCode.notificationAddressP2PKH(); // // txb.addOutput( // notificationAddress, // overrideAmountForTesting ?? _dustLimitP2PKH, // ); // txb.addOutput(opReturnScript, 0); // // // TODO: add possible change output and mark output as dangerous // if (change > 0) { // // generate new change address if current change address has been used // await _checkChangeAddressForTransactions(); // final String changeAddress = await _getCurrentChangeAddress(); // txb.addOutput(changeAddress, change); // } // // txb.sign( // vin: 0, // keyPair: myKeyPair, // witnessValue: utxo.value, // witnessScript: utxoSigningData.first.redeemScript, // ); // // // sign rest of possible inputs // for (var i = 1; i < utxoSigningData.length; i++) { // txb.sign( // vin: i, // keyPair: utxoSigningData[i].keyPair!, // witnessValue: utxoSigningData[i].utxo.value, // witnessScript: utxoSigningData[i].redeemScript, // ); // } // // final builtTx = txb.build(); // // return Tuple2(builtTx.toHex(), builtTx.virtualSize()); // } catch (e, s) { // Logging.instance.log( // "_createNotificationTx(): $e\n$s", // level: LogLevel.Error, // ); // rethrow; // } // } // // Future broadcastNotificationTx({ // required Map preparedTx, // }) async { // try { // Logging.instance.log("confirmNotificationTx txData: $preparedTx", // level: LogLevel.Info); // final txHash = await _electrumXClient.broadcastTransaction( // rawTx: preparedTx["hex"] as String); // Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); // // // TODO: only refresh transaction data // try { // await _refresh(); // } catch (e) { // Logging.instance.log( // "refresh() failed in confirmNotificationTx ($_walletName::$_walletId): $e", // level: LogLevel.Error, // ); // } // // return txHash; // } catch (e, s) { // Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", // level: LogLevel.Error); // rethrow; // } // } // // // Future _checkHasConnectedCache(String paymentCodeString) async { // // final value = await _secureStorage.read( // // key: "$_connectedKeyPrefix$paymentCodeString"); // // if (value == null) { // // return null; // // } else { // // final int rawBool = int.parse(value); // // return rawBool > 0; // // } // // } // // // // Future _setConnectedCache( // // String paymentCodeString, bool hasConnected) async { // // await _secureStorage.write( // // key: "$_connectedKeyPrefix$paymentCodeString", // // value: hasConnected ? "1" : "0"); // // } // // // TODO optimize // Future hasConnected(String paymentCodeString) async { // // final didConnect = await _checkHasConnectedCache(paymentCodeString); // // if (didConnect == true) { // // return true; // // } // // // // final keys = await lookupKey(paymentCodeString); // // // // final tx = await _db // // .getTransactions(_walletId) // // .filter() // // .subTypeEqualTo(TransactionSubType.bip47Notification).and() // // .address((q) => // // q.anyOf(keys, (q, e) => q.otherDataEqualTo(e))) // // .findAll(); // // final myNotificationAddress = await getMyNotificationAddress(); // // final txns = await _db // .getTransactions(_walletId) // .filter() // .subTypeEqualTo(TransactionSubType.bip47Notification) // .findAll(); // // for (final tx in txns) { // if (tx.type == TransactionType.incoming && // tx.address.value?.value == myNotificationAddress.value) { // final unBlindedPaymentCode = await unBlindedPaymentCodeFromTransaction( // transaction: tx, // ); // // if (unBlindedPaymentCode != null && // paymentCodeString == unBlindedPaymentCode.toString()) { // // await _setConnectedCache(paymentCodeString, true); // return true; // } // // final unBlindedPaymentCodeBad = // await unBlindedPaymentCodeFromTransactionBad( // transaction: tx, // ); // // if (unBlindedPaymentCodeBad != null && // paymentCodeString == unBlindedPaymentCodeBad.toString()) { // // await _setConnectedCache(paymentCodeString, true); // return true; // } // } else if (tx.type == TransactionType.outgoing) { // if (tx.address.value?.otherData != null) { // final code = // await paymentCodeStringByKey(tx.address.value!.otherData!); // if (code == paymentCodeString) { // // await _setConnectedCache(paymentCodeString, true); // return true; // } // } // } // } // // // otherwise return no // // await _setConnectedCache(paymentCodeString, false); // return false; // } // // Uint8List? _pubKeyFromInput(Input input) { // final scriptSigComponents = input.scriptSigAsm?.split(" ") ?? []; // if (scriptSigComponents.length > 1) { // return scriptSigComponents[1].fromHex; // } // if (input.witness != null) { // try { // final witnessComponents = jsonDecode(input.witness!) as List; // if (witnessComponents.length == 2) { // return (witnessComponents[1] as String).fromHex; // } // } catch (_) { // // // } // } // return null; // } // // Future unBlindedPaymentCodeFromTransaction({ // required Transaction transaction, // }) async { // try { // final blindedCodeBytes = // Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); // // // transaction does not contain a payment code // if (blindedCodeBytes == null) { // return null; // } // // final designatedInput = transaction.inputs.first; // // final txPoint = designatedInput.txid.fromHex.reversed.toList(); // final txPointIndex = designatedInput.vout; // // final rev = Uint8List(txPoint.length + 4); // Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); // final buffer = rev.buffer.asByteData(); // buffer.setUint32(txPoint.length, txPointIndex, Endian.little); // // final pubKey = _pubKeyFromInput(designatedInput)!; // // final myPrivateKey = (await deriveNotificationBip32Node()).privateKey!; // // final S = SecretPoint(myPrivateKey, pubKey); // // final mask = PaymentCode.getMask(S.ecdhSecret(), rev); // // final unBlindedPayload = PaymentCode.blind( // payload: blindedCodeBytes, // mask: mask, // unBlind: true, // ); // // final unBlindedPaymentCode = PaymentCode.fromPayload( // unBlindedPayload, // networkType: _network, // ); // // return unBlindedPaymentCode; // } catch (e) { // Logging.instance.log( // "unBlindedPaymentCodeFromTransaction() failed: $e\nFor tx: $transaction", // level: LogLevel.Warning, // ); // return null; // } // } // // Future unBlindedPaymentCodeFromTransactionBad({ // required Transaction transaction, // }) async { // try { // final blindedCodeBytes = // Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); // // // transaction does not contain a payment code // if (blindedCodeBytes == null) { // return null; // } // // final designatedInput = transaction.inputs.first; // // final txPoint = designatedInput.txid.fromHex.toList(); // final txPointIndex = designatedInput.vout; // // final rev = Uint8List(txPoint.length + 4); // Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); // final buffer = rev.buffer.asByteData(); // buffer.setUint32(txPoint.length, txPointIndex, Endian.little); // // final pubKey = _pubKeyFromInput(designatedInput)!; // // final myPrivateKey = (await deriveNotificationBip32Node()).privateKey!; // // final S = SecretPoint(myPrivateKey, pubKey); // // final mask = PaymentCode.getMask(S.ecdhSecret(), rev); // // final unBlindedPayload = PaymentCode.blind( // payload: blindedCodeBytes, // mask: mask, // unBlind: true, // ); // // final unBlindedPaymentCode = PaymentCode.fromPayload( // unBlindedPayload, // networkType: _network, // ); // // return unBlindedPaymentCode; // } catch (e) { // Logging.instance.log( // "unBlindedPaymentCodeFromTransactionBad() failed: $e\nFor tx: $transaction", // level: LogLevel.Warning, // ); // return null; // } // } // // Future> // getAllPaymentCodesFromNotificationTransactions() async { // final txns = await _db // .getTransactions(_walletId) // .filter() // .subTypeEqualTo(TransactionSubType.bip47Notification) // .findAll(); // // List codes = []; // // for (final tx in txns) { // // tx is sent so we can check the address's otherData for the code String // if (tx.type == TransactionType.outgoing && // tx.address.value?.otherData != null) { // final codeString = // await paymentCodeStringByKey(tx.address.value!.otherData!); // if (codeString != null && // codes.where((e) => e.toString() == codeString).isEmpty) { // codes.add( // PaymentCode.fromPaymentCode( // codeString, // networkType: _network, // ), // ); // } // } else { // // otherwise we need to un blind the code // final unBlinded = await unBlindedPaymentCodeFromTransaction( // transaction: tx, // ); // if (unBlinded != null && // codes.where((e) => e.toString() == unBlinded.toString()).isEmpty) { // codes.add(unBlinded); // } // // final unBlindedBad = await unBlindedPaymentCodeFromTransactionBad( // transaction: tx, // ); // if (unBlindedBad != null && // codes // .where((e) => e.toString() == unBlindedBad.toString()) // .isEmpty) { // codes.add(unBlindedBad); // } // } // } // // return codes; // } // // Future checkForNotificationTransactionsTo( // Set otherCodeStrings) async { // final sentNotificationTransactions = await _db // .getTransactions(_walletId) // .filter() // .subTypeEqualTo(TransactionSubType.bip47Notification) // .and() // .typeEqualTo(TransactionType.outgoing) // .findAll(); // // final List codes = []; // for (final codeString in otherCodeStrings) { // codes.add(PaymentCode.fromPaymentCode(codeString, networkType: _network)); // } // // for (final tx in sentNotificationTransactions) { // if (tx.address.value != null && tx.address.value!.otherData == null) { // final oldAddress = // await _db.getAddress(_walletId, tx.address.value!.value); // for (final code in codes) { // final notificationAddress = code.notificationAddressP2PKH(); // if (notificationAddress == oldAddress!.value) { // final address = Address( // walletId: _walletId, // value: notificationAddress, // publicKey: [], // derivationIndex: 0, // derivationPath: oldAddress.derivationPath, // type: oldAddress.type, // subType: AddressSubType.paynymNotification, // otherData: await storeCode(code.toString()), // ); // await _db.updateAddress(oldAddress, address); // } // } // } // } // } // // Future restoreAllHistory({ // required int maxUnusedAddressGap, // required int maxNumberOfIndexesToCheck, // required Set paymentCodeStrings, // }) async { // final codes = await getAllPaymentCodesFromNotificationTransactions(); // final List extraCodes = []; // for (final codeString in paymentCodeStrings) { // if (codes.where((e) => e.toString() == codeString).isEmpty) { // final extraCode = PaymentCode.fromPaymentCode( // codeString, // networkType: _network, // ); // if (extraCode.isValid()) { // extraCodes.add(extraCode); // } // } // } // // codes.addAll(extraCodes); // // final List> futures = []; // for (final code in codes) { // futures.add( // restoreHistoryWith( // other: code, // maxUnusedAddressGap: maxUnusedAddressGap, // maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, // checkSegwitAsWell: code.isSegWitEnabled(), // ), // ); // } // // await Future.wait(futures); // } // // Future restoreHistoryWith({ // required PaymentCode other, // required bool checkSegwitAsWell, // required int maxUnusedAddressGap, // required int maxNumberOfIndexesToCheck, // }) async { // // https://en.bitcoin.it/wiki/BIP_0047#Path_levels // const maxCount = 2147483647; // assert(maxNumberOfIndexesToCheck < maxCount); // // final mySendBip32Node = await deriveNotificationBip32Node(); // // List
addresses = []; // int receivingGapCounter = 0; // int outgoingGapCounter = 0; // // // non segwit receiving // for (int i = 0; // i < maxNumberOfIndexesToCheck && // receivingGapCounter < maxUnusedAddressGap; // i++) { // if (receivingGapCounter < maxUnusedAddressGap) { // final address = await _generatePaynymReceivingAddress( // sender: other, // index: i, // generateSegwitAddress: false, // ); // // addresses.add(address); // // final count = await _getTxCount(address: address.value); // // if (count > 0) { // receivingGapCounter = 0; // } else { // receivingGapCounter++; // } // } // } // // // non segwit sends // for (int i = 0; // i < maxNumberOfIndexesToCheck && // outgoingGapCounter < maxUnusedAddressGap; // i++) { // if (outgoingGapCounter < maxUnusedAddressGap) { // final address = await _generatePaynymSendAddress( // other: other, // index: i, // generateSegwitAddress: false, // mySendBip32Node: mySendBip32Node, // ); // // addresses.add(address); // // final count = await _getTxCount(address: address.value); // // if (count > 0) { // outgoingGapCounter = 0; // } else { // outgoingGapCounter++; // } // } // } // // if (checkSegwitAsWell) { // int receivingGapCounterSegwit = 0; // int outgoingGapCounterSegwit = 0; // // segwit receiving // for (int i = 0; // i < maxNumberOfIndexesToCheck && // receivingGapCounterSegwit < maxUnusedAddressGap; // i++) { // if (receivingGapCounterSegwit < maxUnusedAddressGap) { // final address = await _generatePaynymReceivingAddress( // sender: other, // index: i, // generateSegwitAddress: true, // ); // // addresses.add(address); // // final count = await _getTxCount(address: address.value); // // if (count > 0) { // receivingGapCounterSegwit = 0; // } else { // receivingGapCounterSegwit++; // } // } // } // // // segwit sends // for (int i = 0; // i < maxNumberOfIndexesToCheck && // outgoingGapCounterSegwit < maxUnusedAddressGap; // i++) { // if (outgoingGapCounterSegwit < maxUnusedAddressGap) { // final address = await _generatePaynymSendAddress( // other: other, // index: i, // generateSegwitAddress: true, // mySendBip32Node: mySendBip32Node, // ); // // addresses.add(address); // // final count = await _getTxCount(address: address.value); // // if (count > 0) { // outgoingGapCounterSegwit = 0; // } else { // outgoingGapCounterSegwit++; // } // } // } // } // await _db.updateOrPutAddresses(addresses); // } // // Future
getMyNotificationAddress() async { // final storedAddress = await _db // .getAddresses(_walletId) // .filter() // .subTypeEqualTo(AddressSubType.paynymNotification) // .and() // .typeEqualTo(AddressType.p2pkh) // .and() // .not() // .typeEqualTo(AddressType.nonWallet) // .findFirst(); // // if (storedAddress != null) { // return storedAddress; // } else { // final root = await _getRootNode(); // final node = root.derivePath( // _basePaynymDerivePath( // testnet: _coin.isTestNet, // ), // ); // final paymentCode = PaymentCode.fromBip32Node( // node, // networkType: _network, // shouldSetSegwitBit: false, // ); // // final data = btc_dart.PaymentData( // pubkey: paymentCode.notificationPublicKey(), // ); // // final addressString = btc_dart // .P2PKH( // data: data, // network: _network, // ) // .data // .address!; // // Address address = Address( // walletId: _walletId, // value: addressString, // publicKey: paymentCode.getPubKey(), // derivationIndex: 0, // derivationPath: DerivationPath() // ..value = _notificationDerivationPath( // testnet: _coin.isTestNet, // ), // type: AddressType.p2pkh, // subType: AddressSubType.paynymNotification, // otherData: await storeCode(paymentCode.toString()), // ); // // // check against possible race condition. Ff this function was called // // multiple times an address could've been saved after the check at the // // beginning to see if there already was notification address. This would // // lead to a Unique Index violation error // await _db.isar.writeTxn(() async { // final storedAddress = await _db // .getAddresses(_walletId) // .filter() // .subTypeEqualTo(AddressSubType.paynymNotification) // .and() // .typeEqualTo(AddressType.p2pkh) // .and() // .not() // .typeEqualTo(AddressType.nonWallet) // .findFirst(); // // if (storedAddress == null) { // await _db.isar.addresses.put(address); // } else { // address = storedAddress; // } // }); // // return address; // } // } // // /// look up a key that corresponds to a payment code string // Future> lookupKey(String paymentCodeString) async { // final keys = // (await _secureStorage.keys).where((e) => e.startsWith(kPCodeKeyPrefix)); // final List result = []; // for (final key in keys) { // final value = await _secureStorage.read(key: key); // if (value == paymentCodeString) { // result.add(key); // } // } // return result; // } // // /// fetch a payment code string // Future paymentCodeStringByKey(String key) async { // final value = await _secureStorage.read(key: key); // return value; // } // // /// store payment code string and return the generated key used // Future storeCode(String paymentCodeString) async { // final key = _generateKey(); // await _secureStorage.write(key: key, value: paymentCodeString); // return key; // } // // /// generate a new payment code string storage key // String _generateKey() { // final bytes = _randomBytes(24); // return "$kPCodeKeyPrefix${bytes.toHex}"; // } // // // https://github.com/AaronFeickert/stack_wallet_backup/blob/master/lib/secure_storage.dart#L307-L311 // /// Generate cryptographically-secure random bytes // Uint8List _randomBytes(int n) { // final Random rng = Random.secure(); // return Uint8List.fromList( // List.generate(n, (_) => rng.nextInt(0xFF + 1))); // } // }