// import 'dart:convert'; // 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/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/services/coins/dogecoin/dogecoin_wallet.dart'; // import 'package:stackwallet/utilities/bip32_utils.dart'; // import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; // import 'package:stackwallet/utilities/format.dart'; // import 'package:stackwallet/utilities/logger.dart'; // import 'package:tuple/tuple.dart'; // // const kPaynymDerivePath = "m/47'/0'/0'"; // // extension PayNym on DogecoinWallet { // // generate bip32 payment code root // Future getRootNode({ // required List mnemonic, // }) async { // final root = await Bip32Utils.getBip32Root(mnemonic.join(" "), network); // return root; // } // // Future deriveNotificationPrivateKey({ // required List mnemonic, // }) async { // final root = await getRootNode(mnemonic: mnemonic); // final node = root.derivePath(kPaynymDerivePath).derive(0); // return node.privateKey!; // } // // /// fetch or generate this wallet's bip47 payment code // Future getPaymentCode( // DerivePathType derivePathType, // ) async { // final address = await getMyNotificationAddress(derivePathType); // final paymentCode = PaymentCode.fromPaymentCode( // address.otherData!, // network, // ); // return paymentCode; // } // // Future signWithNotificationKey(Uint8List data) async { // final privateKey = // await deriveNotificationPrivateKey(mnemonic: await mnemonic); // final pair = btc_dart.ECPair.fromPrivateKey(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 int satoshiAmount, // Map? args}) async { // if (!(await hasConnected(paymentCode.notificationAddressP2PKH()))) { // throw PaynymSendException( // "No notification transaction sent to $paymentCode"); // } else { // final myPrivateKey = // await deriveNotificationPrivateKey(mnemonic: await mnemonic); // final sendToAddress = await nextUnusedSendAddressFrom( // pCode: paymentCode, // privateKey: myPrivateKey, // ); // // return prepareSend( // address: sendToAddress.value, satoshiAmount: satoshiAmount); // } // } // // /// 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 Uint8List privateKey, // int startIndex = 0, // }) async { // // https://en.bitcoin.it/wiki/BIP_0047#Path_levels // const maxCount = 2147483647; // // for (int i = startIndex; i < maxCount; i++) { // final address = await db // .getAddresses(walletId) // .filter() // .subTypeEqualTo(AddressSubType.paynymSend) // .and() // .otherDataEqualTo(pCode.toString()) // .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 pair = PaymentAddress.initWithPrivateKey( // privateKey, // pCode, // i, // index to use // ).getSendAddressKeyPair(); // // // add address to local db // final address = generatePaynymSendAddressFromKeyPair( // pair: pair, // derivationIndex: i, // derivePathType: DerivePathType.bip44, // toPaymentCode: pCode, // ); // await db.putAddress(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 { // const amountToSend = DUST_LIMIT; // final List availableOutputs = utxos ?? await this.utxos; // 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 chainHeight, MINIMUM_CONFIRMATIONS) == // 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, // utxosToUse: utxoObjectsToUse, // utxoSigningData: utxoSigningData, // change: 0)) // .item2; // // final int vSizeForWithChange = (await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxosToUse: utxoObjectsToUse, // 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 (feeForNoChange < vSizeForNoChange * 1000) { // feeForNoChange = vSizeForNoChange * 1000; // } // if (feeForWithChange < vSizeForWithChange * 1000) { // feeForWithChange = vSizeForWithChange * 1000; // } // // if (satoshisBeingUsed - amountToSend > feeForNoChange + DUST_LIMIT) { // // 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 >= DUST_LIMIT && // satoshisBeingUsed - amountToSend - changeAmount == feeForWithChange) { // final txn = await _createNotificationTx( // targetPaymentCodeString: targetPaymentCodeString, // utxosToUse: utxoObjectsToUse, // utxoSigningData: utxoSigningData, // change: changeAmount, // ); // // int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount; // // Map transactionObject = { // "hex": txn.item1, // "recipientPaynym": targetPaymentCodeString, // "amount": amountToSend, // "fee": feeBeingPaid, // "vSize": txn.item2, // }; // return transactionObject; // } 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, // utxosToUse: utxoObjectsToUse, // utxoSigningData: utxoSigningData, // change: 0, // ); // // int feeBeingPaid = satoshisBeingUsed - amountToSend; // // Map transactionObject = { // "hex": txn.item1, // "recipientPaynym": targetPaymentCodeString, // "amount": amountToSend, // "fee": feeBeingPaid, // "vSize": txn.item2, // }; // return transactionObject; // } // } 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, // utxosToUse: utxoObjectsToUse, // utxoSigningData: utxoSigningData, // change: 0, // ); // // int feeBeingPaid = satoshisBeingUsed - amountToSend; // // Map transactionObject = { // "hex": txn.item1, // "recipientPaynym": targetPaymentCodeString, // "amount": amountToSend, // "fee": feeBeingPaid, // "vSize": txn.item2, // }; // return transactionObject; // } 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."); // } // } // } // // // 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 utxosToUse, // required Map utxoSigningData, // required int change, // }) async { // final targetPaymentCode = // PaymentCode.fromPaymentCode(targetPaymentCodeString, network); // final myCode = await getPaymentCode(DerivePathType.bip44); // // final utxo = utxosToUse.first; // final txPoint = utxo.txid.fromHex.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[utxo.txid]["keyPair"] as btc_dart.ECPair; // // final S = SecretPoint( // myKeyPair.privateKey!, // targetPaymentCode.notificationPublicKey(), // ); // // final blindingMask = PaymentCode.getMask(S.ecdhSecret(), rev); // // final blindedPaymentCode = PaymentCode.blind( // myCode.getPayload(), // blindingMask, // ); // // 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, // ); // // // todo: modify address once segwit support is in our bip47 // txb.addOutput(targetPaymentCode.notificationAddressP2PKH(), DUST_LIMIT); // 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 currentChangeAddress; // txb.addOutput(changeAddress, change); // } // // txb.sign( // vin: 0, // keyPair: myKeyPair, // ); // // // sign rest of possible inputs // for (var i = 1; i < utxosToUse.length - 1; i++) { // final txid = utxosToUse[i].txid; // txb.sign( // vin: i, // keyPair: utxoSigningData[txid]["keyPair"] as btc_dart.ECPair, // // witnessValue: utxosToUse[i].value, // ); // } // // final builtTx = txb.build(); // // return Tuple2(builtTx.toHex(), builtTx.virtualSize()); // } // // 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; // } // } // // // TODO optimize // Future hasConnected(String paymentCodeString) async { // final myNotificationAddress = // await getMyNotificationAddress(DerivePathTypeExt.primaryFor(coin)); // // final txns = await db // .getTransactions(walletId) // .filter() // .subTypeEqualTo(TransactionSubType.bip47Notification) // .findAll(); // // for (final tx in txns) { // // quick check that may cause problems? // if (tx.address.value?.value == myNotificationAddress.value) { // return true; // } // // final unBlindedPaymentCode = await unBlindedPaymentCodeFromTransaction( // transaction: tx, // myNotificationAddress: myNotificationAddress, // ); // // if (paymentCodeString == unBlindedPaymentCode.toString()) { // return true; // } // } // // // otherwise return no // return false; // } // // Future unBlindedPaymentCodeFromTransaction({ // required Transaction transaction, // required Address myNotificationAddress, // }) async { // if (transaction.address.value != null && // transaction.address.value!.value != myNotificationAddress.value) { // return null; // } // // try { // final blindedCode = // transaction.outputs.elementAt(1).scriptPubKeyAsm!.split(" ")[1]; // // 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 = designatedInput.scriptSigAsm!.split(" ")[1].fromHex; // // final myPrivateKey = // await deriveNotificationPrivateKey(mnemonic: await mnemonic); // // final S = SecretPoint(myPrivateKey, pubKey); // // final mask = PaymentCode.getMask(S.ecdhSecret(), rev); // // final unBlindedPayload = PaymentCode.blind(blindedCode.fromHex, mask); // // final unBlindedPaymentCode = // PaymentCode.initFromPayload(unBlindedPayload); // // return unBlindedPaymentCode; // } catch (e) { // Logging.instance.log( // "unBlindedPaymentCodeFromTransaction() failed: $e", // level: LogLevel.Warning, // ); // return null; // } // } // // Future> // getAllPaymentCodesFromNotificationTransactions() async { // final myAddress = // await getMyNotificationAddress(DerivePathTypeExt.primaryFor(coin)); // final txns = await db // .getTransactions(walletId) // .filter() // .subTypeEqualTo(TransactionSubType.bip47Notification) // .findAll(); // // List unBlindedList = []; // // for (final tx in txns) { // final unBlinded = await unBlindedPaymentCodeFromTransaction( // transaction: tx, // myNotificationAddress: myAddress, // ); // if (unBlinded != null) { // unBlindedList.add(unBlinded); // } // } // // return unBlindedList; // } // // Future restoreHistoryWith( // PaymentCode other, // int maxUnusedAddressGap, // int maxNumberOfIndexesToCheck, // ) async { // // https://en.bitcoin.it/wiki/BIP_0047#Path_levels // const maxCount = 2147483647; // assert(maxNumberOfIndexesToCheck < maxCount); // // final myPrivateKey = // await deriveNotificationPrivateKey(mnemonic: await mnemonic); // // List
addresses = []; // int receivingGapCounter = 0; // int outgoingGapCounter = 0; // // for (int i = 0; // i < maxNumberOfIndexesToCheck && // (receivingGapCounter < maxUnusedAddressGap || // outgoingGapCounter < maxUnusedAddressGap); // i++) { // final paymentAddress = PaymentAddress.initWithPrivateKey( // myPrivateKey, // other, // i, // index to use // ); // // if (receivingGapCounter < maxUnusedAddressGap) { // final pair = paymentAddress.getSendAddressKeyPair(); // final address = generatePaynymSendAddressFromKeyPair( // pair: pair, // derivationIndex: i, // derivePathType: DerivePathType.bip44, // toPaymentCode: other, // ); // addresses.add(address); // // final count = await getTxCount(address: address.value); // // if (count > 0) { // receivingGapCounter++; // } else { // receivingGapCounter = 0; // } // } // // if (outgoingGapCounter < maxUnusedAddressGap) { // final pair = paymentAddress.getReceiveAddressKeyPair(); // final address = generatePaynymReceivingAddressFromKeyPair( // pair: pair, // derivationIndex: i, // derivePathType: DerivePathType.bip44, // fromPaymentCode: other, // ); // addresses.add(address); // // final count = await getTxCount(address: address.value); // // if (count > 0) { // outgoingGapCounter++; // } else { // outgoingGapCounter = 0; // } // } // } // await db.putAddresses(addresses); // } // // Address generatePaynymSendAddressFromKeyPair({ // required btc_dart.ECPair pair, // required int derivationIndex, // required DerivePathType derivePathType, // required PaymentCode toPaymentCode, // }) { // final data = btc_dart.PaymentData(pubkey: pair.publicKey); // // String addressString; // switch (derivePathType) { // case DerivePathType.bip44: // addressString = // btc_dart.P2PKH(data: data, network: network).data.address!; // break; // // // The following doesn't apply currently // // case DerivePathType.bip49: // // addressString = btc_dart // // .P2SH( // // data: btc_dart.PaymentData( // // redeem: btc_dart // // .P2WPKH( // // data: data, // // network: network, // // ) // // .data), // // network: network, // // ) // // .data // // .address!; // // break; // // // // case DerivePathType.bip84: // // addressString = btc_dart // // .P2WPKH( // // network: network, // // data: data, // // ) // // .data // // .address!; // // break; // default: // throw UnimplementedError("segwit paynyms not implemented yet"); // } // // final address = Address( // walletId: walletId, // value: addressString, // publicKey: pair.publicKey, // derivationIndex: derivationIndex, // type: AddressType.nonWallet, // subType: AddressSubType.paynymSend, // otherData: toPaymentCode.toString(), // ); // // return address; // } // // Address generatePaynymReceivingAddressFromKeyPair({ // required btc_dart.ECPair pair, // required int derivationIndex, // required DerivePathType derivePathType, // required PaymentCode fromPaymentCode, // }) { // final data = btc_dart.PaymentData(pubkey: pair.publicKey); // // String addressString; // AddressType addrType; // switch (derivePathType) { // case DerivePathType.bip44: // addressString = btc_dart // .P2PKH( // data: data, // network: network, // ) // .data // .address!; // addrType = AddressType.p2pkh; // break; // // // The following doesn't apply currently // // case DerivePathType.bip49: // // addressString = btc_dart // // .P2SH( // // data: btc_dart.PaymentData( // // redeem: btc_dart // // .P2WPKH( // // data: data, // // network: network, // // ) // // .data), // // network: network, // // ) // // .data // // .address!; // // addrType = AddressType.p2sh; // // break; // // // // case DerivePathType.bip84: // // addressString = btc_dart // // .P2WPKH( // // network: network, // // data: data, // // ) // // .data // // .address!; // // addrType = AddressType.p2wpkh; // // break; // default: // throw UnimplementedError("segwit paynyms not implemented yet"); // } // // final address = Address( // walletId: walletId, // value: addressString, // publicKey: pair.publicKey, // derivationIndex: derivationIndex, // type: addrType, // subType: AddressSubType.paynymReceive, // otherData: fromPaymentCode.toString(), // ); // // return address; // } // // Future
getMyNotificationAddress( // DerivePathType derivePathType, // ) async { // // TODO: fix when segwit is here // derivePathType = DerivePathType.bip44; // // AddressType type; // switch (derivePathType) { // case DerivePathType.bip44: // type = AddressType.p2pkh; // break; // case DerivePathType.bip49: // type = AddressType.p2sh; // break; // case DerivePathType.bip84: // type = AddressType.p2wpkh; // break; // } // // final storedAddress = await db // .getAddresses(walletId) // .filter() // .subTypeEqualTo(AddressSubType.paynymNotification) // .and() // .typeEqualTo(type) // .and() // .not() // .typeEqualTo(AddressType.nonWallet) // .findFirst(); // // if (storedAddress != null) { // return storedAddress; // } else { // final root = await getRootNode(mnemonic: await mnemonic); // final node = root.derivePath(kPaynymDerivePath); // final paymentCode = PaymentCode.initFromPubKey( // node.publicKey, // node.chainCode, // network, // ); // // String addressString; // final data = // btc_dart.PaymentData(pubkey: paymentCode.notificationPublicKey()); // switch (derivePathType) { // case DerivePathType.bip44: // addressString = btc_dart // .P2PKH( // data: data, // network: network, // ) // .data // .address!; // break; // // case DerivePathType.bip49: // // addressString = btc_dart // // .P2SH( // // data: btc_dart.PaymentData( // // redeem: btc_dart // // .P2WPKH( // // data: data, // // network: network, // // ) // // .data), // // network: network, // // ) // // .data // // .address!; // // break; // // case DerivePathType.bip84: // // addressString = btc_dart // // .P2WPKH( // // network: network, // // data: data, // // ) // // .data // // .address!; // // break; // default: // throw UnimplementedError("segwit paynyms not implemented yet"); // } // // final address = Address( // walletId: walletId, // value: addressString, // publicKey: paymentCode.getPubKey(), // derivationIndex: 0, // type: type, // subType: AddressSubType.paynymNotification, // otherData: paymentCode.toString(), // ); // // await db.putAddress(address); // return address; // } // } // }