add initial segwit paynym support

This commit is contained in:
julian 2023-04-25 10:45:02 -06:00
parent 6fb93b00ff
commit 5ad161e6a9
3 changed files with 316 additions and 253 deletions

View file

@ -20,7 +20,6 @@ import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/bip32_utils.dart';
import 'package:stackwallet/utilities/bip47_utils.dart'; import 'package:stackwallet/utilities/bip47_utils.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
@ -30,9 +29,10 @@ const String kPaynymDerivePath = "m/47'/0'/0'";
const String kPaynymNotificationDerivationPath = "$kPaynymDerivePath/0"; const String kPaynymNotificationDerivationPath = "$kPaynymDerivePath/0";
const String kPCodeKeyPrefix = "pCode_key_"; const String kPCodeKeyPrefix = "pCode_key_";
String _receivingPaynymAddressDerivationPath(int index) { String _receivingPaynymAddressDerivationPath(int index) =>
return "$kPaynymDerivePath/$index/0"; "$kPaynymDerivePath/$index/0";
} String _sendPaynymAddressDerivationPath(int index) =>
"$kPaynymDerivePath/0/$index";
mixin PaynymWalletInterface { mixin PaynymWalletInterface {
// passed in wallet data // passed in wallet data
@ -43,6 +43,7 @@ mixin PaynymWalletInterface {
late final MainDB _db; late final MainDB _db;
late final ElectrumX _electrumXClient; late final ElectrumX _electrumXClient;
late final SecureStorageInterface _secureStorage; late final SecureStorageInterface _secureStorage;
late final int _dustLimit;
late final int _dustLimitP2PKH; late final int _dustLimitP2PKH;
late final int _minConfirms; late final int _minConfirms;
@ -78,6 +79,7 @@ mixin PaynymWalletInterface {
required MainDB db, required MainDB db,
required ElectrumX electrumXClient, required ElectrumX electrumXClient,
required SecureStorageInterface secureStorage, required SecureStorageInterface secureStorage,
required int dustLimit,
required int dustLimitP2PKH, required int dustLimitP2PKH,
required int minConfirms, required int minConfirms,
required Future<String?> Function() getMnemonicString, required Future<String?> Function() getMnemonicString,
@ -113,6 +115,7 @@ mixin PaynymWalletInterface {
_db = db; _db = db;
_electrumXClient = electrumXClient; _electrumXClient = electrumXClient;
_secureStorage = secureStorage; _secureStorage = secureStorage;
_dustLimitP2PKH = dustLimit;
_dustLimitP2PKH = dustLimitP2PKH; _dustLimitP2PKH = dustLimitP2PKH;
_minConfirms = minConfirms; _minConfirms = minConfirms;
_getMnemonicString = getMnemonicString; _getMnemonicString = getMnemonicString;
@ -130,19 +133,38 @@ mixin PaynymWalletInterface {
// convenience getter // convenience getter
btc_dart.NetworkType get networkType => _network; btc_dart.NetworkType get networkType => _network;
Future<Address> currentReceivingPaynymAddress(PaymentCode sender) async { Future<Address> currentReceivingPaynymAddress({
required PaymentCode sender,
required bool isSegwit,
}) async {
final keys = await lookupKey(sender.toString()); final keys = await lookupKey(sender.toString());
final address = await _db final address = await _db
.getAddresses(_walletId) .getAddresses(_walletId)
.filter() .filter()
.subTypeEqualTo(AddressSubType.paynymReceive) .subTypeEqualTo(AddressSubType.paynymReceive)
.and() .and()
.group((q) {
if (isSegwit) {
return q
.typeEqualTo(AddressType.p2sh)
.or()
.typeEqualTo(AddressType.p2wpkh);
} else {
return q.typeEqualTo(AddressType.p2pkh);
}
})
.and()
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e)) .anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
.sortByDerivationIndexDesc() .sortByDerivationIndexDesc()
.findFirst(); .findFirst();
if (address == null) { if (address == null) {
final generatedAddress = await _generatePaynymReceivingAddress(sender, 0); final generatedAddress = await _generatePaynymReceivingAddress(
sender: sender,
index: 0,
generateSegwitAddress: isSegwit,
);
final existing = await _db final existing = await _db
.getAddresses(_walletId) .getAddresses(_walletId)
@ -158,16 +180,20 @@ mixin PaynymWalletInterface {
await _db.updateAddress(existing, generatedAddress); await _db.updateAddress(existing, generatedAddress);
} }
return currentReceivingPaynymAddress(sender); return currentReceivingPaynymAddress(
isSegwit: isSegwit,
sender: sender,
);
} else { } else {
return address; return address;
} }
} }
Future<Address> _generatePaynymReceivingAddress( Future<Address> _generatePaynymReceivingAddress({
PaymentCode sender, required PaymentCode sender,
int index, required int index,
) async { required bool generateSegwitAddress,
}) async {
final myPrivateKeyNode = await deriveReceivingPrivateKeyNode( final myPrivateKeyNode = await deriveReceivingPrivateKeyNode(
mnemonic: (await _getMnemonicString())!, mnemonic: (await _getMnemonicString())!,
mnemonicPassphrase: (await _getMnemonicPassphrase())!, mnemonicPassphrase: (await _getMnemonicPassphrase())!,
@ -184,21 +210,27 @@ mixin PaynymWalletInterface {
final address = await generatePaynymReceivingAddressFromKeyPair( final address = await generatePaynymReceivingAddressFromKeyPair(
pair: pair, pair: pair,
derivationIndex: index, derivationIndex: index,
derivePathType: DerivePathType.bip44, isSegwit: generateSegwitAddress,
fromPaymentCode: sender, fromPaymentCode: sender,
); );
return address; return address;
} }
Future<void> checkCurrentPaynymReceivingAddressForTransactions( Future<void> checkCurrentPaynymReceivingAddressForTransactions({
PaymentCode sender) async { required PaymentCode sender,
final address = await currentReceivingPaynymAddress(sender); required bool isSegwit,
}) async {
final address = await currentReceivingPaynymAddress(
sender: sender,
isSegwit: isSegwit,
);
final txCount = await _getTxCount(address: address.value); final txCount = await _getTxCount(address: address.value);
if (txCount > 0) { if (txCount > 0) {
// generate next address and add to db // generate next address and add to db
final nextAddress = await _generatePaynymReceivingAddress( final nextAddress = await _generatePaynymReceivingAddress(
sender, sender: sender,
address.derivationIndex + 1, index: address.derivationIndex + 1,
generateSegwitAddress: isSegwit,
); );
final existing = await _db final existing = await _db
@ -215,7 +247,10 @@ mixin PaynymWalletInterface {
await _db.updateAddress(existing, nextAddress); await _db.updateAddress(existing, nextAddress);
} }
// keep checking until address with no tx history is set as current // keep checking until address with no tx history is set as current
await checkCurrentPaynymReceivingAddressForTransactions(sender); await checkCurrentPaynymReceivingAddressForTransactions(
sender: sender,
isSegwit: isSegwit,
);
} }
} }
@ -223,7 +258,14 @@ mixin PaynymWalletInterface {
final codes = await getAllPaymentCodesFromNotificationTransactions(); final codes = await getAllPaymentCodesFromNotificationTransactions();
final List<Future<void>> futures = []; final List<Future<void>> futures = [];
for (final code in codes) { for (final code in codes) {
futures.add(checkCurrentPaynymReceivingAddressForTransactions(code)); futures.add(checkCurrentPaynymReceivingAddressForTransactions(
sender: code,
isSegwit: true,
));
futures.add(checkCurrentPaynymReceivingAddressForTransactions(
sender: code,
isSegwit: false,
));
} }
await Future.wait(futures); await Future.wait(futures);
} }
@ -233,14 +275,15 @@ mixin PaynymWalletInterface {
required String mnemonic, required String mnemonic,
required String mnemonicPassphrase, required String mnemonicPassphrase,
}) async { }) async {
final root = await Bip32Utils.getBip32Root( return _cachedRootNode ??= await Bip32Utils.getBip32Root(
mnemonic, mnemonic,
mnemonicPassphrase, mnemonicPassphrase,
_network, _network,
); );
return root;
} }
bip32.BIP32? _cachedRootNode;
Future<bip32.BIP32> deriveNotificationBip32Node({ Future<bip32.BIP32> deriveNotificationBip32Node({
required String mnemonic, required String mnemonic,
required String mnemonicPassphrase, required String mnemonicPassphrase,
@ -267,15 +310,16 @@ mixin PaynymWalletInterface {
} }
/// fetch or generate this wallet's bip47 payment code /// fetch or generate this wallet's bip47 payment code
Future<PaymentCode> getPaymentCode( Future<PaymentCode> getPaymentCode({
DerivePathType derivePathType, [ required bool isSegwit,
bip32.BIP32? bip32Root, }) async {
]) async { final address = await getMyNotificationAddress(
final address = await getMyNotificationAddress(derivePathType, bip32Root); isSegwit: isSegwit,
);
final pCodeString = await paymentCodeStringByKey(address.otherData!); final pCodeString = await paymentCodeStringByKey(address.otherData!);
final paymentCode = PaymentCode.fromPaymentCode( final paymentCode = PaymentCode.fromPaymentCode(
pCodeString!, pCodeString!,
_network, networkType: _network,
); );
return paymentCode; return paymentCode;
} }
@ -299,6 +343,7 @@ mixin PaynymWalletInterface {
Future<Map<String, dynamic>> preparePaymentCodeSend({ Future<Map<String, dynamic>> preparePaymentCodeSend({
required PaymentCode paymentCode, required PaymentCode paymentCode,
required bool isSegwit,
required Amount amount, required Amount amount,
Map<String, dynamic>? args, Map<String, dynamic>? args,
}) async { }) async {
@ -313,6 +358,7 @@ mixin PaynymWalletInterface {
final sendToAddress = await nextUnusedSendAddressFrom( final sendToAddress = await nextUnusedSendAddressFrom(
pCode: paymentCode, pCode: paymentCode,
privateKeyNode: myPrivateKeyNode, privateKeyNode: myPrivateKeyNode,
isSegwit: isSegwit,
); );
return _prepareSend( return _prepareSend(
@ -327,6 +373,7 @@ mixin PaynymWalletInterface {
/// and your own private key /// and your own private key
Future<Address> nextUnusedSendAddressFrom({ Future<Address> nextUnusedSendAddressFrom({
required PaymentCode pCode, required PaymentCode pCode,
required bool isSegwit,
required bip32.BIP32 privateKeyNode, required bip32.BIP32 privateKeyNode,
int startIndex = 0, int startIndex = 0,
}) async { }) async {
@ -363,8 +410,8 @@ mixin PaynymWalletInterface {
final address = await generatePaynymSendAddressFromKeyPair( final address = await generatePaynymSendAddressFromKeyPair(
pair: pair, pair: pair,
derivationIndex: i, derivationIndex: i,
derivePathType: DerivePathType.bip44,
toPaymentCode: pCode, toPaymentCode: pCode,
isSegwit: isSegwit,
); );
final storedAddress = await _db.getAddress(_walletId, address.value); final storedAddress = await _db.getAddress(_walletId, address.value);
@ -387,6 +434,7 @@ mixin PaynymWalletInterface {
Future<Map<String, dynamic>> prepareNotificationTx({ Future<Map<String, dynamic>> prepareNotificationTx({
required int selectedTxFeeRate, required int selectedTxFeeRate,
required String targetPaymentCodeString, required String targetPaymentCodeString,
required bool isSegwit,
int additionalOutputs = 0, int additionalOutputs = 0,
List<UTXO>? utxos, List<UTXO>? utxos,
}) async { }) async {
@ -449,8 +497,7 @@ mixin PaynymWalletInterface {
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
utxoSigningData: utxoSigningData, utxoSigningData: utxoSigningData,
change: 0, change: 0,
dustLimit: isSegwit: isSegwit,
satoshisBeingUsed, // override amount to get around absurd fees error
)) ))
.item2; .item2;
@ -458,6 +505,7 @@ mixin PaynymWalletInterface {
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
utxoSigningData: utxoSigningData, utxoSigningData: utxoSigningData,
change: satoshisBeingUsed - amountToSend, change: satoshisBeingUsed - amountToSend,
isSegwit: isSegwit,
)) ))
.item2; .item2;
@ -495,6 +543,7 @@ mixin PaynymWalletInterface {
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
utxoSigningData: utxoSigningData, utxoSigningData: utxoSigningData,
change: changeAmount, change: changeAmount,
isSegwit: isSegwit,
); );
int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount; int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount;
@ -507,6 +556,7 @@ mixin PaynymWalletInterface {
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
utxoSigningData: utxoSigningData, utxoSigningData: utxoSigningData,
change: changeAmount, change: changeAmount,
isSegwit: isSegwit,
); );
} }
@ -526,6 +576,7 @@ mixin PaynymWalletInterface {
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
utxoSigningData: utxoSigningData, utxoSigningData: utxoSigningData,
change: 0, change: 0,
isSegwit: isSegwit,
); );
int feeBeingPaid = satoshisBeingUsed - amountToSend; int feeBeingPaid = satoshisBeingUsed - amountToSend;
@ -547,6 +598,7 @@ mixin PaynymWalletInterface {
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
utxoSigningData: utxoSigningData, utxoSigningData: utxoSigningData,
change: 0, change: 0,
isSegwit: isSegwit,
); );
int feeBeingPaid = satoshisBeingUsed - amountToSend; int feeBeingPaid = satoshisBeingUsed - amountToSend;
@ -568,6 +620,7 @@ mixin PaynymWalletInterface {
selectedTxFeeRate: selectedTxFeeRate, selectedTxFeeRate: selectedTxFeeRate,
targetPaymentCodeString: targetPaymentCodeString, targetPaymentCodeString: targetPaymentCodeString,
additionalOutputs: additionalOutputs + 1, additionalOutputs: additionalOutputs + 1,
isSegwit: isSegwit,
); );
} else { } else {
throw InsufficientBalanceException( throw InsufficientBalanceException(
@ -583,14 +636,16 @@ mixin PaynymWalletInterface {
// equal to its vSize // equal to its vSize
Future<Tuple2<String, int>> _createNotificationTx({ Future<Tuple2<String, int>> _createNotificationTx({
required String targetPaymentCodeString, required String targetPaymentCodeString,
required bool isSegwit,
required List<SigningData> utxoSigningData, required List<SigningData> utxoSigningData,
required int change, required int change,
int? dustLimit,
}) async { }) async {
try { try {
final targetPaymentCode = final targetPaymentCode = PaymentCode.fromPaymentCode(
PaymentCode.fromPaymentCode(targetPaymentCodeString, _network); targetPaymentCodeString,
final myCode = await getPaymentCode(DerivePathType.bip44); networkType: _network,
);
final myCode = await getPaymentCode(isSegwit: isSegwit);
final utxo = utxoSigningData.first.utxo; final utxo = utxoSigningData.first.utxo;
final txPoint = utxo.txid.fromHex.reversed.toList(); final txPoint = utxo.txid.fromHex.reversed.toList();
@ -642,11 +697,21 @@ mixin PaynymWalletInterface {
utxoSigningData[i].output!, utxoSigningData[i].output!,
); );
} }
final String notificationAddress;
if (isSegwit) {
final publicKey = targetPaymentCode.derivePublicKey(0);
final data = btc_dart.P2WPKH(
data: btc_dart.PaymentData(pubkey: publicKey),
network: networkType,
);
notificationAddress = data.data.address!;
} else {
notificationAddress = targetPaymentCode.notificationAddressP2PKH();
}
// todo: modify address once segwit support is in our bip47
txb.addOutput( txb.addOutput(
targetPaymentCode.notificationAddressP2PKH(), notificationAddress,
dustLimit ?? _dustLimitP2PKH, isSegwit ? _dustLimit : _dustLimitP2PKH,
); );
txb.addOutput(opReturnScript, 0); txb.addOutput(opReturnScript, 0);
@ -687,8 +752,9 @@ mixin PaynymWalletInterface {
} }
} }
Future<String> broadcastNotificationTx( Future<String> broadcastNotificationTx({
{required Map<String, dynamic> preparedTx}) async { required Map<String, dynamic> preparedTx,
}) async {
try { try {
Logging.instance.log("confirmNotificationTx txData: $preparedTx", Logging.instance.log("confirmNotificationTx txData: $preparedTx",
level: LogLevel.Info); level: LogLevel.Info);
@ -750,7 +816,9 @@ mixin PaynymWalletInterface {
// .findAll(); // .findAll();
final myNotificationAddress = final myNotificationAddress =
await getMyNotificationAddress(DerivePathTypeExt.primaryFor(_coin)); await getMyNotificationAddress(isSegwit: false);
final myNotificationAddressSegwit =
await getMyNotificationAddress(isSegwit: true);
final txns = await _db final txns = await _db
.getTransactions(_walletId) .getTransactions(_walletId)
@ -760,10 +828,10 @@ mixin PaynymWalletInterface {
for (final tx in txns) { for (final tx in txns) {
if (tx.type == TransactionType.incoming && if (tx.type == TransactionType.incoming &&
tx.address.value?.value == myNotificationAddress.value) { (tx.address.value?.value == myNotificationAddress.value ||
tx.address.value?.value == myNotificationAddressSegwit.value)) {
final unBlindedPaymentCode = await unBlindedPaymentCodeFromTransaction( final unBlindedPaymentCode = await unBlindedPaymentCodeFromTransaction(
transaction: tx, transaction: tx,
myNotificationAddress: myNotificationAddress,
); );
if (unBlindedPaymentCode != null && if (unBlindedPaymentCode != null &&
@ -775,7 +843,6 @@ mixin PaynymWalletInterface {
final unBlindedPaymentCodeBad = final unBlindedPaymentCodeBad =
await unBlindedPaymentCodeFromTransactionBad( await unBlindedPaymentCodeFromTransactionBad(
transaction: tx, transaction: tx,
myNotificationAddress: myNotificationAddress,
); );
if (unBlindedPaymentCodeBad != null && if (unBlindedPaymentCodeBad != null &&
@ -820,13 +887,7 @@ mixin PaynymWalletInterface {
Future<PaymentCode?> unBlindedPaymentCodeFromTransaction({ Future<PaymentCode?> unBlindedPaymentCodeFromTransaction({
required Transaction transaction, required Transaction transaction,
required Address myNotificationAddress,
}) async { }) async {
if (transaction.address.value != null &&
transaction.address.value!.value != myNotificationAddress.value) {
return null;
}
try { try {
final blindedCodeBytes = final blindedCodeBytes =
Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction);
@ -864,7 +925,10 @@ mixin PaynymWalletInterface {
unBlind: true, unBlind: true,
); );
final unBlindedPaymentCode = PaymentCode.fromPayload(unBlindedPayload); final unBlindedPaymentCode = PaymentCode.fromPayload(
unBlindedPayload,
networkType: _network,
);
return unBlindedPaymentCode; return unBlindedPaymentCode;
} catch (e) { } catch (e) {
@ -878,13 +942,7 @@ mixin PaynymWalletInterface {
Future<PaymentCode?> unBlindedPaymentCodeFromTransactionBad({ Future<PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
required Transaction transaction, required Transaction transaction,
required Address myNotificationAddress,
}) async { }) async {
if (transaction.address.value != null &&
transaction.address.value!.value != myNotificationAddress.value) {
return null;
}
try { try {
final blindedCodeBytes = final blindedCodeBytes =
Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction);
@ -922,7 +980,10 @@ mixin PaynymWalletInterface {
unBlind: true, unBlind: true,
); );
final unBlindedPaymentCode = PaymentCode.fromPayload(unBlindedPayload); final unBlindedPaymentCode = PaymentCode.fromPayload(
unBlindedPayload,
networkType: _network,
);
return unBlindedPaymentCode; return unBlindedPaymentCode;
} catch (e) { } catch (e) {
@ -936,8 +997,6 @@ mixin PaynymWalletInterface {
Future<List<PaymentCode>> Future<List<PaymentCode>>
getAllPaymentCodesFromNotificationTransactions() async { getAllPaymentCodesFromNotificationTransactions() async {
final myAddress =
await getMyNotificationAddress(DerivePathTypeExt.primaryFor(_coin));
final txns = await _db final txns = await _db
.getTransactions(_walletId) .getTransactions(_walletId)
.filter() .filter()
@ -954,13 +1013,17 @@ mixin PaynymWalletInterface {
await paymentCodeStringByKey(tx.address.value!.otherData!); await paymentCodeStringByKey(tx.address.value!.otherData!);
if (codeString != null && if (codeString != null &&
codes.where((e) => e.toString() == codeString).isEmpty) { codes.where((e) => e.toString() == codeString).isEmpty) {
codes.add(PaymentCode.fromPaymentCode(codeString, _network)); codes.add(
PaymentCode.fromPaymentCode(
codeString,
networkType: _network,
),
);
} }
} else { } else {
// otherwise we need to un blind the code // otherwise we need to un blind the code
final unBlinded = await unBlindedPaymentCodeFromTransaction( final unBlinded = await unBlindedPaymentCodeFromTransaction(
transaction: tx, transaction: tx,
myNotificationAddress: myAddress,
); );
if (unBlinded != null && if (unBlinded != null &&
codes.where((e) => e.toString() == unBlinded.toString()).isEmpty) { codes.where((e) => e.toString() == unBlinded.toString()).isEmpty) {
@ -969,7 +1032,6 @@ mixin PaynymWalletInterface {
final unBlindedBad = await unBlindedPaymentCodeFromTransactionBad( final unBlindedBad = await unBlindedPaymentCodeFromTransactionBad(
transaction: tx, transaction: tx,
myNotificationAddress: myAddress,
); );
if (unBlindedBad != null && if (unBlindedBad != null &&
codes codes
@ -995,7 +1057,7 @@ mixin PaynymWalletInterface {
final List<PaymentCode> codes = []; final List<PaymentCode> codes = [];
for (final codeString in otherCodeStrings) { for (final codeString in otherCodeStrings) {
codes.add(PaymentCode.fromPaymentCode(codeString, _network)); codes.add(PaymentCode.fromPaymentCode(codeString, networkType: _network));
} }
for (final tx in sentNotificationTransactions) { for (final tx in sentNotificationTransactions) {
@ -1031,7 +1093,10 @@ mixin PaynymWalletInterface {
final List<PaymentCode> extraCodes = []; final List<PaymentCode> extraCodes = [];
for (final codeString in paymentCodeStrings) { for (final codeString in paymentCodeStrings) {
if (codes.where((e) => e.toString() == codeString).isEmpty) { if (codes.where((e) => e.toString() == codeString).isEmpty) {
final extraCode = PaymentCode.fromPaymentCode(codeString, _network); final extraCode = PaymentCode.fromPaymentCode(
codeString,
networkType: _network,
);
if (extraCode.isValid()) { if (extraCode.isValid()) {
extraCodes.add(extraCode); extraCodes.add(extraCode);
} }
@ -1044,9 +1109,10 @@ mixin PaynymWalletInterface {
for (final code in codes) { for (final code in codes) {
futures.add( futures.add(
restoreHistoryWith( restoreHistoryWith(
code, other: code,
maxUnusedAddressGap, maxUnusedAddressGap: maxUnusedAddressGap,
maxNumberOfIndexesToCheck, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
checkSegwitAsWell: code.isSegWitEnabled(),
), ),
); );
} }
@ -1054,11 +1120,12 @@ mixin PaynymWalletInterface {
await Future.wait(futures); await Future.wait(futures);
} }
Future<void> restoreHistoryWith( Future<void> restoreHistoryWith({
PaymentCode other, required PaymentCode other,
int maxUnusedAddressGap, required bool checkSegwitAsWell,
int maxNumberOfIndexesToCheck, required int maxUnusedAddressGap,
) async { required int maxNumberOfIndexesToCheck,
}) async {
// https://en.bitcoin.it/wiki/BIP_0047#Path_levels // https://en.bitcoin.it/wiki/BIP_0047#Path_levels
const maxCount = 2147483647; const maxCount = 2147483647;
assert(maxNumberOfIndexesToCheck < maxCount); assert(maxNumberOfIndexesToCheck < maxCount);
@ -1080,36 +1147,11 @@ mixin PaynymWalletInterface {
int receivingGapCounter = 0; int receivingGapCounter = 0;
int outgoingGapCounter = 0; int outgoingGapCounter = 0;
// non segwit receiving
for (int i = 0; for (int i = 0;
i < maxNumberOfIndexesToCheck && i < maxNumberOfIndexesToCheck &&
(receivingGapCounter < maxUnusedAddressGap || receivingGapCounter < maxUnusedAddressGap;
outgoingGapCounter < maxUnusedAddressGap);
i++) { i++) {
if (outgoingGapCounter < maxUnusedAddressGap) {
final paymentAddressSending = PaymentAddress(
paymentCode: other,
bip32Node: mySendBip32Node,
index: i,
networkType: networkType,
);
final pair = paymentAddressSending.getSendAddressKeyPair();
final address = await generatePaynymSendAddressFromKeyPair(
pair: pair,
derivationIndex: i,
derivePathType: DerivePathType.bip44,
toPaymentCode: other,
);
addresses.add(address);
final count = await _getTxCount(address: address.value);
if (count > 0) {
outgoingGapCounter = 0;
} else {
outgoingGapCounter++;
}
}
if (receivingGapCounter < maxUnusedAddressGap) { if (receivingGapCounter < maxUnusedAddressGap) {
final paymentAddressReceiving = PaymentAddress( final paymentAddressReceiving = PaymentAddress(
paymentCode: other, paymentCode: other,
@ -1122,7 +1164,7 @@ mixin PaynymWalletInterface {
final address = await generatePaynymReceivingAddressFromKeyPair( final address = await generatePaynymReceivingAddressFromKeyPair(
pair: pair, pair: pair,
derivationIndex: i, derivationIndex: i,
derivePathType: DerivePathType.bip44, isSegwit: false,
fromPaymentCode: other, fromPaymentCode: other,
); );
addresses.add(address); addresses.add(address);
@ -1136,52 +1178,131 @@ mixin PaynymWalletInterface {
} }
} }
} }
// non segwit sends
for (int i = 0;
i < maxNumberOfIndexesToCheck &&
outgoingGapCounter < maxUnusedAddressGap;
i++) {
if (outgoingGapCounter < maxUnusedAddressGap) {
final pair = PaymentAddress(
paymentCode: other,
bip32Node: mySendBip32Node,
index: i,
networkType: networkType,
).getSendAddressKeyPair();
final address = await generatePaynymSendAddressFromKeyPair(
pair: pair,
derivationIndex: i,
isSegwit: false,
toPaymentCode: other,
);
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 paymentAddressReceiving = PaymentAddress(
paymentCode: other,
bip32Node: receivingNode.derive(i),
index: 0,
networkType: networkType,
);
final pair = paymentAddressReceiving.getReceiveAddressKeyPair();
final address = await generatePaynymReceivingAddressFromKeyPair(
pair: pair,
derivationIndex: i,
isSegwit: true,
fromPaymentCode: other,
);
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 pair = PaymentAddress(
paymentCode: other,
bip32Node: mySendBip32Node,
index: i,
networkType: networkType,
).getSendAddressKeyPair();
final address = await generatePaynymSendAddressFromKeyPair(
pair: pair,
derivationIndex: i,
isSegwit: true,
toPaymentCode: other,
);
addresses.add(address);
final count = await _getTxCount(address: address.value);
if (count > 0) {
outgoingGapCounterSegwit = 0;
} else {
outgoingGapCounterSegwit++;
}
}
}
}
await _db.updateOrPutAddresses(addresses); await _db.updateOrPutAddresses(addresses);
} }
Future<Address> generatePaynymSendAddressFromKeyPair({ Future<Address> generatePaynymSendAddressFromKeyPair({
required btc_dart.ECPair pair, required btc_dart.ECPair pair,
required int derivationIndex, required int derivationIndex,
required DerivePathType derivePathType, required bool isSegwit,
required PaymentCode toPaymentCode, required PaymentCode toPaymentCode,
}) async { }) async {
final data = btc_dart.PaymentData(pubkey: pair.publicKey); final data = btc_dart.PaymentData(pubkey: pair.publicKey);
String addressString; final String addressString;
switch (derivePathType) {
case DerivePathType.bip44:
addressString =
btc_dart.P2PKH(data: data, network: _network).data.address!;
break;
// The following doesn't apply currently if (isSegwit) {
// case DerivePathType.bip49: addressString = btc_dart
// addressString = btc_dart .P2WPKH(
// .P2SH( network: _network,
// data: btc_dart.PaymentData( data: data,
// redeem: btc_dart )
// .P2WPKH( .data
// data: data, .address!;
// network: network, } else {
// ) addressString = btc_dart
// .data), .P2PKH(
// network: network, data: data,
// ) network: _network,
// .data )
// .address!; .data
// break; .address!;
//
// 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( final address = Address(
@ -1189,8 +1310,8 @@ mixin PaynymWalletInterface {
value: addressString, value: addressString,
publicKey: pair.publicKey, publicKey: pair.publicKey,
derivationIndex: derivationIndex, derivationIndex: derivationIndex,
derivationPath: derivationPath: DerivationPath()
null, // might as well use null due to complexity of context ..value = _sendPaynymAddressDerivationPath(derivationIndex),
type: AddressType.nonWallet, type: AddressType.nonWallet,
subType: AddressSubType.paynymSend, subType: AddressSubType.paynymSend,
otherData: await storeCode(toPaymentCode.toString()), otherData: await storeCode(toPaymentCode.toString()),
@ -1202,55 +1323,32 @@ mixin PaynymWalletInterface {
Future<Address> generatePaynymReceivingAddressFromKeyPair({ Future<Address> generatePaynymReceivingAddressFromKeyPair({
required btc_dart.ECPair pair, required btc_dart.ECPair pair,
required int derivationIndex, required int derivationIndex,
required DerivePathType derivePathType, required bool isSegwit,
required PaymentCode fromPaymentCode, required PaymentCode fromPaymentCode,
}) async { }) async {
final data = btc_dart.PaymentData(pubkey: pair.publicKey); final data = btc_dart.PaymentData(pubkey: pair.publicKey);
String addressString; final String addressString;
AddressType addrType; final AddressType addressType;
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 if (isSegwit) {
// case DerivePathType.bip49: addressString = btc_dart
// addressString = btc_dart .P2WPKH(
// .P2SH( network: _network,
// data: btc_dart.PaymentData( data: data,
// redeem: btc_dart )
// .P2WPKH( .data
// data: data, .address!;
// network: network, addressType = AddressType.p2wpkh;
// ) } else {
// .data), addressString = btc_dart
// network: network, .P2PKH(
// ) data: data,
// .data network: _network,
// .address!; )
// addrType = AddressType.p2sh; .data
// break; .address!;
// addressType = AddressType.p2pkh;
// 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( final address = Address(
@ -1260,12 +1358,12 @@ mixin PaynymWalletInterface {
derivationIndex: derivationIndex, derivationIndex: derivationIndex,
derivationPath: DerivationPath() derivationPath: DerivationPath()
..value = _receivingPaynymAddressDerivationPath(derivationIndex), ..value = _receivingPaynymAddressDerivationPath(derivationIndex),
type: addrType, type: addressType,
subType: AddressSubType.paynymReceive, subType: AddressSubType.paynymReceive,
otherData: await storeCode(fromPaymentCode.toString()), otherData: await storeCode(fromPaymentCode.toString()),
); );
final myCode = await getPaymentCode(DerivePathType.bip44); final myCode = await getPaymentCode(isSegwit: isSegwit);
final bip32NetworkType = bip32.NetworkType( final bip32NetworkType = bip32.NetworkType(
wif: _network.wif, wif: _network.wif,
@ -1284,27 +1382,10 @@ mixin PaynymWalletInterface {
return address; return address;
} }
Future<Address> getMyNotificationAddress( Future<Address> getMyNotificationAddress({
DerivePathType derivePathType, [ required bool isSegwit,
bip32.BIP32? bip32Root, }) async {
]) async { final AddressType type = isSegwit ? AddressType.p2wpkh : AddressType.p2pkh;
// 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;
default:
throw Exception("DerivePathType $derivePathType not supported");
}
final storedAddress = await _db final storedAddress = await _db
.getAddresses(_walletId) .getAddresses(_walletId)
@ -1320,56 +1401,38 @@ mixin PaynymWalletInterface {
if (storedAddress != null) { if (storedAddress != null) {
return storedAddress; return storedAddress;
} else { } else {
final root = bip32Root ?? final root = await _getRootNode(
await _getRootNode( mnemonic: (await _getMnemonicString())!,
mnemonic: (await _getMnemonicString())!, mnemonicPassphrase: (await _getMnemonicPassphrase())!,
mnemonicPassphrase: (await _getMnemonicPassphrase())!, );
);
final node = root.derivePath(kPaynymDerivePath); final node = root.derivePath(kPaynymDerivePath);
final paymentCode = PaymentCode.fromBip32Node( final paymentCode = PaymentCode.fromBip32Node(
node, node,
_network, networkType: _network,
shouldSetSegwitBit: isSegwit,
); );
String addressString; final String addressString;
final data = final data = btc_dart.PaymentData(
btc_dart.PaymentData(pubkey: paymentCode.notificationPublicKey()); pubkey: paymentCode.notificationPublicKey(),
switch (derivePathType) { );
case DerivePathType.bip44:
addressString = btc_dart if (isSegwit) {
.P2PKH( addressString = btc_dart
data: data, .P2WPKH(
network: _network, network: _network,
) data: data,
.data )
.address!; .data
break; .address!;
// case DerivePathType.bip49: } else {
// addressString = btc_dart addressString = btc_dart
// .P2SH( .P2PKH(
// data: btc_dart.PaymentData( data: data,
// redeem: btc_dart network: _network,
// .P2WPKH( )
// data: data, .data
// network: network, .address!;
// )
// .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( final address = Address(

View file

@ -111,11 +111,11 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "48dd65f88822fba8543826274f6d51c17f735f93" ref: "3e27b910d92e2ab816d589d202161490c00f986b"
resolved-ref: "48dd65f88822fba8543826274f6d51c17f735f93" resolved-ref: "3e27b910d92e2ab816d589d202161490c00f986b"
url: "https://github.com/cypherstack/bip47.git" url: "https://github.com/cypherstack/bip47.git"
source: git source: git
version: "1.0.0" version: "2.0.0"
bitbox: bitbox:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -60,7 +60,7 @@ dependencies:
bip47: bip47:
git: git:
url: https://github.com/cypherstack/bip47.git url: https://github.com/cypherstack/bip47.git
ref: 48dd65f88822fba8543826274f6d51c17f735f93 ref: 3e27b910d92e2ab816d589d202161490c00f986b
# Utility plugins # Utility plugins
# provider: ^6.0.1 # provider: ^6.0.1