no logging within computes and use bip32 for derivations as it is more permissive (than coinlib) which lelantus requires

This commit is contained in:
julian 2023-11-24 11:56:18 -06:00
parent 56b9e1f851
commit e78af049ee

View file

@ -1,11 +1,12 @@
import 'package:bip32/bip32.dart';
import 'package:bitcoindart/bitcoindart.dart' as bitcoindart; import 'package:bitcoindart/bitcoindart.dart' as bitcoindart;
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:lelantus/lelantus.dart' as lelantus; import 'package:lelantus/lelantus.dart' as lelantus;
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/lelantus_fee_data.dart'; import 'package:stackwallet/models/lelantus_fee_data.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/extensions/impl/string.dart';
import 'package:stackwallet/utilities/extensions/impl/uint8_list.dart'; import 'package:stackwallet/utilities/extensions/impl/uint8_list.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';
@ -44,8 +45,15 @@ abstract final class LelantusFfiWrapper {
walletId: walletId, walletId: walletId,
partialDerivationPath: partialDerivationPath, partialDerivationPath: partialDerivationPath,
); );
try {
return await compute(_restore, args); return await compute(_restore, args);
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from _restore(): $e\n$s",
level: LogLevel.Info,
);
rethrow;
}
} }
// partialDerivationPath should be something like "m/$purpose'/$coinType'/$account'/" // partialDerivationPath should be something like "m/$purpose'/$coinType'/$account'/"
@ -68,56 +76,96 @@ abstract final class LelantusFfiWrapper {
int lastFoundIndex = 0; int lastFoundIndex = 0;
int currentIndex = 0; int currentIndex = 0;
try { Set<String> usedSerialNumbersSet = args.usedSerialNumbers.toSet();
Set<String> usedSerialNumbersSet = args.usedSerialNumbers.toSet();
final root = coinlib.HDPrivateKey.fromKeyAndChainCode( final root = BIP32.fromPrivateKey(
coinlib.ECPrivateKey.fromHex(args.hexRootPrivateKey), args.hexRootPrivateKey.toUint8ListFromHex,
args.chaincode, args.chaincode,
);
while (currentIndex < lastFoundIndex + 50) {
final _derivePath =
"${args.partialDerivationPath}$MINT_INDEX/$currentIndex";
final mintKeyPair = root.derivePath(_derivePath);
final String mintTag = lelantus.CreateTag(
mintKeyPair.privateKey!.toHex,
currentIndex,
Format.uint8listToString(mintKeyPair.identifier),
isTestnet: args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
); );
while (currentIndex < lastFoundIndex + 50) { for (int setId = 1; setId <= args.latestSetId; setId++) {
final _derivePath = final setData = args.setDataMap[setId] as Map;
"${args.partialDerivationPath}$MINT_INDEX/$currentIndex"; final foundCoin = (setData["coins"] as List).firstWhere(
(e) => e[1] == mintTag,
final mintKeyPair = root.derivePath(_derivePath); orElse: () => <Object>[],
final String mintTag = lelantus.CreateTag(
mintKeyPair.privateKey.data.toHex,
// Format.uint8listToString(mintKeyPair.privateKey!),
currentIndex,
Format.uint8listToString(mintKeyPair.identifier),
isTestnet: args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
); );
for (int setId = 1; setId <= args.latestSetId; setId++) { if (foundCoin.length == 4) {
final setData = args.setDataMap[setId] as Map; lastFoundIndex = currentIndex;
final foundCoin = (setData["coins"] as List).firstWhere(
(e) => e[1] == mintTag,
orElse: () => <Object>[],
);
if (foundCoin.length == 4) { final String publicCoin = foundCoin[0] as String;
lastFoundIndex = currentIndex; final String txId = foundCoin[3] as String;
final String publicCoin = foundCoin[0] as String; // this value will either be an int or a String
final String txId = foundCoin[3] as String; final dynamic thirdValue = foundCoin[2];
// this value will either be an int or a String if (thirdValue is int) {
final dynamic thirdValue = foundCoin[2]; final int amount = thirdValue;
final String serialNumber = lelantus.GetSerialNumber(
amount,
mintKeyPair.privateKey!.toHex,
currentIndex,
isTestnet:
args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
);
final bool isUsed = usedSerialNumbersSet.contains(serialNumber);
lelantusCoins.removeWhere((e) =>
e.txid == txId &&
e.mintIndex == currentIndex &&
e.anonymitySetId != setId);
lelantusCoins.add(
isar_models.LelantusCoin(
walletId: args.walletId,
mintIndex: currentIndex,
value: amount.toString(),
txid: txId,
anonymitySetId: setId,
isUsed: isUsed,
isJMint: false,
otherData:
publicCoin, // not really needed but saved just in case
),
);
debugPrint("amount $amount used $isUsed");
} else if (thirdValue is String) {
final int keyPath = lelantus.GetAesKeyPath(publicCoin);
final derivePath =
"${args.partialDerivationPath}$JMINT_INDEX/$keyPath";
final aesKeyPair = root.derivePath(derivePath);
try {
final String aesPrivateKey = aesKeyPair.privateKey!.toHex;
final int amount = lelantus.decryptMintAmount(
aesPrivateKey,
thirdValue,
);
if (thirdValue is int) {
final int amount = thirdValue;
final String serialNumber = lelantus.GetSerialNumber( final String serialNumber = lelantus.GetSerialNumber(
amount, amount,
mintKeyPair.privateKey.data.toHex, aesPrivateKey,
// Format.uint8listToString(mintKeyPair.privateKey!),
currentIndex, currentIndex,
isTestnet: isTestnet:
args.cryptoCurrency.network == CryptoCurrencyNetwork.test, args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
); );
final bool isUsed = usedSerialNumbersSet.contains(serialNumber); bool isUsed = usedSerialNumbersSet.contains(serialNumber);
lelantusCoins.removeWhere((e) => lelantusCoins.removeWhere((e) =>
e.txid == txId && e.txid == txId &&
e.mintIndex == currentIndex && e.mintIndex == currentIndex &&
@ -131,81 +179,25 @@ abstract final class LelantusFfiWrapper {
txid: txId, txid: txId,
anonymitySetId: setId, anonymitySetId: setId,
isUsed: isUsed, isUsed: isUsed,
isJMint: false, isJMint: true,
otherData: otherData:
publicCoin, // not really needed but saved just in case publicCoin, // not really needed but saved just in case
), ),
); );
Logging.instance.log( jindexes.add(currentIndex);
"amount $amount used $isUsed",
level: LogLevel.Info,
);
} else if (thirdValue is String) {
final int keyPath = lelantus.GetAesKeyPath(publicCoin);
final derivePath = spendTxIds.add(txId);
"${args.partialDerivationPath}$JMINT_INDEX/$keyPath"; } catch (_) {
debugPrint(
final aesKeyPair = root.derivePath(derivePath); "AES keypair derivation issue for derive path: $derivePath");
try {
final String aesPrivateKey = aesKeyPair.privateKey.data.toHex;
final int amount = lelantus.decryptMintAmount(
aesPrivateKey,
thirdValue,
);
final String serialNumber = lelantus.GetSerialNumber(
amount,
aesKeyPair.privateKey.data.toHex,
currentIndex,
isTestnet:
args.cryptoCurrency.network == CryptoCurrencyNetwork.test,
);
bool isUsed = usedSerialNumbersSet.contains(serialNumber);
lelantusCoins.removeWhere((e) =>
e.txid == txId &&
e.mintIndex == currentIndex &&
e.anonymitySetId != setId);
lelantusCoins.add(
isar_models.LelantusCoin(
walletId: args.walletId,
mintIndex: currentIndex,
value: amount.toString(),
txid: txId,
anonymitySetId: setId,
isUsed: isUsed,
isJMint: true,
otherData:
publicCoin, // not really needed but saved just in case
),
);
jindexes.add(currentIndex);
spendTxIds.add(txId);
} catch (_) {
Logging.instance.log(
"AES keypair derivation issue for derive path: $derivePath",
level: LogLevel.Warning,
);
}
} else {
Logging.instance.log(
"Unexpected coin found: $foundCoin",
level: LogLevel.Warning,
);
} }
} else {
debugPrint("Unexpected coin found: $foundCoin");
} }
} }
currentIndex++;
} }
} catch (e, s) {
Logging.instance.log("Exception rethrown from isolateRestore(): $e\n$s", currentIndex++;
level: LogLevel.Info);
rethrow;
} }
return (spendTxIds: spendTxIds, lelantusCoins: lelantusCoins); return (spendTxIds: spendTxIds, lelantusCoins: lelantusCoins);
@ -235,13 +227,12 @@ abstract final class LelantusFfiWrapper {
List<lelantus.DartLelantusEntry> lelantusEntries, List<lelantus.DartLelantusEntry> lelantusEntries,
bool isTestNet, bool isTestNet,
}) data) async { }) data) async {
Logging.instance.log("estimateJoinsplit fee", level: LogLevel.Info); debugPrint("estimateJoinSplit fee");
// for (int i = 0; i < lelantusEntries.length; i++) { // for (int i = 0; i < lelantusEntries.length; i++) {
// Logging.instance.log(lelantusEntries[i], addToDebugMessagesDB: false); // Logging.instance.log(lelantusEntries[i], addToDebugMessagesDB: false);
// } // }
Logging.instance.log( debugPrint(
"${data.spendAmount} ${data.subtractFeeFromAmount}", "${data.spendAmount} ${data.subtractFeeFromAmount}",
level: LogLevel.Info,
); );
List<int> changeToMint = List.empty(growable: true); List<int> changeToMint = List.empty(growable: true);
@ -256,13 +247,15 @@ abstract final class LelantusFfiWrapper {
isTestnet: data.isTestNet, isTestnet: data.isTestNet,
); );
final estimateFeeData = final estimateFeeData = LelantusFeeData(
LelantusFeeData(changeToMint[0], fee, spendCoinIndexes); changeToMint[0],
Logging.instance.log( fee,
spendCoinIndexes,
);
debugPrint(
"estimateFeeData ${estimateFeeData.changeToMint}" "estimateFeeData ${estimateFeeData.changeToMint}"
" ${estimateFeeData.fee}" " ${estimateFeeData.fee}"
" ${estimateFeeData.spendCoinIndexes}", " ${estimateFeeData.spendCoinIndexes}",
level: LogLevel.Info,
); );
return estimateFeeData; return estimateFeeData;
} }
@ -322,8 +315,7 @@ abstract final class LelantusFfiWrapper {
var changeToMint = estimateJoinSplitFee.changeToMint; var changeToMint = estimateJoinSplitFee.changeToMint;
var fee = estimateJoinSplitFee.fee; var fee = estimateJoinSplitFee.fee;
var spendCoinIndexes = estimateJoinSplitFee.spendCoinIndexes; var spendCoinIndexes = estimateJoinSplitFee.spendCoinIndexes;
Logging.instance debugPrint("$changeToMint $fee $spendCoinIndexes");
.log("$changeToMint $fee $spendCoinIndexes", level: LogLevel.Info);
if (spendCoinIndexes.isEmpty) { if (spendCoinIndexes.isEmpty) {
throw Exception("Error, Not enough funds."); throw Exception("Error, Not enough funds.");
} }
@ -354,14 +346,14 @@ abstract final class LelantusFfiWrapper {
); );
final derivePath = "${arg.partialDerivationPath}$MINT_INDEX/${arg.index}"; final derivePath = "${arg.partialDerivationPath}$MINT_INDEX/${arg.index}";
final root = coinlib.HDPrivateKey.fromKeyAndChainCode( final root = BIP32.fromPrivateKey(
coinlib.ECPrivateKey.fromHex(arg.hexRootPrivateKey), arg.hexRootPrivateKey.toUint8ListFromHex,
arg.chaincode, arg.chaincode,
); );
final jmintKeyPair = root.derivePath(derivePath); final jmintKeyPair = root.derivePath(derivePath);
final String jmintprivatekey = jmintKeyPair.privateKey.data.toHex; final String jmintprivatekey = jmintKeyPair.privateKey!.toHex;
final keyPath = lelantus.getMintKeyPath( final keyPath = lelantus.getMintKeyPath(
changeToMint, changeToMint,
@ -373,7 +365,7 @@ abstract final class LelantusFfiWrapper {
final _derivePath = "${arg.partialDerivationPath}$JMINT_INDEX/$keyPath"; final _derivePath = "${arg.partialDerivationPath}$JMINT_INDEX/$keyPath";
final aesKeyPair = root.derivePath(_derivePath); final aesKeyPair = root.derivePath(_derivePath);
final aesPrivateKey = aesKeyPair.privateKey.data.toHex; final aesPrivateKey = aesKeyPair.privateKey!.toHex;
final jmintData = lelantus.createJMintScript( final jmintData = lelantus.createJMintScript(
changeToMint, changeToMint,
@ -467,8 +459,6 @@ abstract final class LelantusFfiWrapper {
final txHex = extTx.toHex(); final txHex = extTx.toHex();
final txId = extTx.getId(); final txId = extTx.getId();
Logging.instance.log("txid $txId", level: LogLevel.Info);
Logging.instance.log("txHex: $txHex", level: LogLevel.Info);
final amountAmount = Amount( final amountAmount = Amount(
rawValue: BigInt.from(amount), rawValue: BigInt.from(amount),