From 6253652c21cfe4d175e0ea2898585a1cdeb821c8 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 23 Jan 2023 16:11:24 -0600 Subject: [PATCH] detect paynym notification transactions --- lib/services/coins/coin_paynym_extension.dart | 62 ++++++++++++++++--- .../coins/dogecoin/dogecoin_wallet.dart | 6 +- lib/services/mixins/electrum_x_parsing.dart | 23 ++++--- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/lib/services/coins/coin_paynym_extension.dart b/lib/services/coins/coin_paynym_extension.dart index 2ad0e4475..b2ccfda9e 100644 --- a/lib/services/coins/coin_paynym_extension.dart +++ b/lib/services/coins/coin_paynym_extension.dart @@ -28,7 +28,7 @@ extension PayNym on DogecoinWallet { return root; } - // fetch or generate this wallet's bip47 payment code + /// fetch or generate this wallet's bip47 payment code Future getPaymentCode() async { final address = await db .getAddresses(walletId) @@ -75,7 +75,7 @@ extension PayNym on DogecoinWallet { } void preparePaymentCodeSend(PaymentCode pCode) async { - if (!hasConnected(pCode.notificationAddress())) { + if (!(await hasConnected(pCode.notificationAddress()))) { throw PaynymSendException("No notification transaction sent to $pCode"); } else { final root = await getRootNode(mnemonic: await mnemonic); @@ -433,12 +433,56 @@ extension PayNym on DogecoinWallet { } } - bool hasConnected(String paymentCodeString) { - return db - .getTransactions(walletId) - .filter() - .address((q) => q.valueEqualTo(paymentCodeString)) - .countSync() > - 0; + // TODO optimize + Future hasConnected(String paymentCodeString) async { + final myCode = await getPaymentCode(); + final myNotificationAddress = myCode.notificationAddress(); + + final txns = await db + .getTransactions(walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .findAll(); + + for (final tx in txns) { + if (tx.address.value?.value == myNotificationAddress) { + return true; + } + + final blindedCode = + tx.outputs.elementAt(1).scriptPubKeyAsm!.split(" ")[1]; + + final designatedInput = tx.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 root = await getRootNode(mnemonic: await mnemonic); + final myPrivateKey = + root.derivePath(kPaynymDerivePath).derive(0).privateKey!; + + final S = SecretPoint(myPrivateKey, pubKey); + + final mask = PaymentCode.getMask(S.ecdhSecret(), rev); + + final unBlindedPayload = PaymentCode.blind(blindedCode.fromHex, mask); + + final unBlindedPaymentCode = + PaymentCode.initFromPayload(unBlindedPayload); + + if (paymentCodeString == unBlindedPaymentCode.toString()) { + return true; + } + } + + // otherwise return no + return false; } } diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 7d0fdea6f..c47ad8947 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1162,10 +1162,8 @@ class DogecoinWallet extends CoinServiceAPI .not() .typeEqualTo(isar_models.AddressType.nonWallet) .and() - .group((q) => q - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .or() - .subTypeEqualTo(isar_models.AddressSubType.change)) + .not() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet) .findAll(); return allAddresses; } diff --git a/lib/services/mixins/electrum_x_parsing.dart b/lib/services/mixins/electrum_x_parsing.dart index 49f28a50c..b9b815594 100644 --- a/lib/services/mixins/electrum_x_parsing.dart +++ b/lib/services/mixins/electrum_x_parsing.dart @@ -1,3 +1,4 @@ +import 'package:bip47/src/util.dart'; import 'package:decimal/decimal.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -148,10 +149,6 @@ mixin ElectrumXParsing { amount = amountReceivedInWallet; } - bool isNotificationTx = coin.hasPaynymSupport && - type == TransactionType.incoming && - transactionAddress.subType == AddressSubType.paynymNotification; - List outs = []; List ins = []; @@ -188,15 +185,27 @@ mixin ElectrumXParsing { outs.add(output); } + TransactionSubType txSubType = TransactionSubType.none; + if (coin.hasPaynymSupport && outs.length > 1) { + List? scriptChunks = outs[1].scriptPubKeyAsm?.split(" "); + if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") { + final blindedPaymentCode = scriptChunks![1]; + final bytes = blindedPaymentCode.fromHex; + + // https://en.bitcoin.it/wiki/BIP_0047#Sending + if (bytes.length == 80 && bytes.first == 1) { + txSubType = TransactionSubType.bip47Notification; + } + } + } + final tx = Transaction( walletId: walletId, txid: txData["txid"] as String, timestamp: txData["blocktime"] as int? ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000), type: type, - subType: isNotificationTx - ? TransactionSubType.bip47Notification - : TransactionSubType.none, + subType: txSubType, amount: amount, fee: fee, height: txData["height"] as int?,