Update frost and hide insecure addresses on receive page and in address list

This commit is contained in:
julian 2024-09-20 17:04:49 -06:00 committed by julian-CStack
parent 94757f0f2e
commit 412e3fdf07
9 changed files with 297 additions and 17 deletions

@ -1 +1 @@
Subproject commit d3fa8169524376579da08a5062ad88e45ee9b1eb
Subproject commit 2451deab817b456ad93d5579c0d0687cb681392a

View file

@ -11,6 +11,7 @@
import 'dart:convert';
import 'package:isar/isar.dart';
import '../../../../exceptions/address/address_exception.dart';
import 'crypto_currency_address.dart';
import 'transaction.dart';
@ -27,6 +28,7 @@ class Address extends CryptoCurrencyAddress {
required this.derivationPath,
required this.type,
required this.subType,
this.zSafeFrost,
this.otherData,
});
@ -55,6 +57,8 @@ class Address extends CryptoCurrencyAddress {
final transactions = IsarLinks<Transaction>();
late final bool? zSafeFrost;
int derivationChain() {
if (subType == AddressSubType.receiving) {
return 0; // 0 for receiving (external)
@ -80,6 +84,7 @@ class Address extends CryptoCurrencyAddress {
AddressType? type,
AddressSubType? subType,
DerivationPath? derivationPath,
bool? zSafeFrost,
String? otherData,
}) {
return Address(
@ -90,6 +95,7 @@ class Address extends CryptoCurrencyAddress {
type: type ?? this.type,
subType: subType ?? this.subType,
derivationPath: derivationPath ?? this.derivationPath,
zSafeFrost: zSafeFrost ?? this.zSafeFrost,
otherData: otherData ?? this.otherData,
);
}
@ -105,6 +111,7 @@ class Address extends CryptoCurrencyAddress {
"subType: ${subType.name}, "
"transactionsLength: ${transactions.length} "
"derivationPath: $derivationPath, "
"zSafeFrost: $zSafeFrost, "
"otherData: $otherData, "
"}";
@ -117,6 +124,7 @@ class Address extends CryptoCurrencyAddress {
"type": type.name,
"subType": subType.name,
"derivationPath": derivationPath?.value,
"zSafeFrost": zSafeFrost,
"otherData": otherData,
};
return jsonEncode(result);
@ -143,6 +151,7 @@ class Address extends CryptoCurrencyAddress {
derivationPath: derivationPath,
type: AddressType.values.byName(json["type"] as String),
subType: AddressSubType.values.byName(json["subType"] as String),
zSafeFrost: json["zSafeFrost"] as bool?,
otherData: json["otherData"] as String?,
);
}

View file

@ -59,6 +59,11 @@ const AddressSchema = CollectionSchema(
id: 7,
name: r'walletId',
type: IsarType.string,
),
r'zSafeFrost': PropertySchema(
id: 8,
name: r'zSafeFrost',
type: IsarType.bool,
)
},
estimateSize: _addressEstimateSize,
@ -172,6 +177,7 @@ void _addressSerialize(
writer.writeByte(offsets[5], object.type.index);
writer.writeString(offsets[6], object.value);
writer.writeString(offsets[7], object.walletId);
writer.writeBool(offsets[8], object.zSafeFrost);
}
Address _addressDeserialize(
@ -195,6 +201,7 @@ Address _addressDeserialize(
AddressType.p2pkh,
value: reader.readString(offsets[6]),
walletId: reader.readString(offsets[7]),
zSafeFrost: reader.readBoolOrNull(offsets[8]),
);
object.id = id;
return object;
@ -229,6 +236,8 @@ P _addressDeserializeProp<P>(
return (reader.readString(offset)) as P;
case 7:
return (reader.readString(offset)) as P;
case 8:
return (reader.readBoolOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -1474,6 +1483,32 @@ extension AddressQueryFilter
));
});
}
QueryBuilder<Address, Address, QAfterFilterCondition> zSafeFrostIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'zSafeFrost',
));
});
}
QueryBuilder<Address, Address, QAfterFilterCondition> zSafeFrostIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'zSafeFrost',
));
});
}
QueryBuilder<Address, Address, QAfterFilterCondition> zSafeFrostEqualTo(
bool? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'zSafeFrost',
value: value,
));
});
}
}
extension AddressQueryObject
@ -1621,6 +1656,18 @@ extension AddressQuerySortBy on QueryBuilder<Address, Address, QSortBy> {
return query.addSortBy(r'walletId', Sort.desc);
});
}
QueryBuilder<Address, Address, QAfterSortBy> sortByZSafeFrost() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'zSafeFrost', Sort.asc);
});
}
QueryBuilder<Address, Address, QAfterSortBy> sortByZSafeFrostDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'zSafeFrost', Sort.desc);
});
}
}
extension AddressQuerySortThenBy
@ -1708,6 +1755,18 @@ extension AddressQuerySortThenBy
return query.addSortBy(r'walletId', Sort.desc);
});
}
QueryBuilder<Address, Address, QAfterSortBy> thenByZSafeFrost() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'zSafeFrost', Sort.asc);
});
}
QueryBuilder<Address, Address, QAfterSortBy> thenByZSafeFrostDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'zSafeFrost', Sort.desc);
});
}
}
extension AddressQueryWhereDistinct
@ -1756,6 +1815,12 @@ extension AddressQueryWhereDistinct
return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive);
});
}
QueryBuilder<Address, Address, QDistinct> distinctByZSafeFrost() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'zSafeFrost');
});
}
}
extension AddressQueryProperty
@ -1814,6 +1879,12 @@ extension AddressQueryProperty
return query.addPropertyName(r'walletId');
});
}
QueryBuilder<Address, bool?, QQueryOperations> zSafeFrostProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'zSafeFrost');
});
}
}
// **************************************************************************

View file

@ -8,6 +8,7 @@
*
*/
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
@ -372,6 +373,16 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
detail: address.subType.prettyName,
button: Container(),
),
if (kDebugMode)
const _Div(
height: 12,
),
if (kDebugMode)
DetailItem(
title: "frost secure (kDebugMode)",
detail: address.zSafeFrost.toString(),
button: Container(),
),
if (ref.watch(pWallets).getWallet(widget.walletId)
is Bip39HDWallet)
const _Div(

View file

@ -67,6 +67,19 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
.and()
.not()
.typeEqualTo(AddressType.nonWallet)
.and()
.group(
(q) => q
.group(
(q2) => q2
.typeEqualTo(AddressType.frostMS)
.and()
.zSafeFrostEqualTo(true),
)
.or()
.not()
.typeEqualTo(AddressType.frostMS),
)
.sortByDerivationIndex()
.idProperty()
.findAll();
@ -114,6 +127,19 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
.and()
.not()
.typeEqualTo(AddressType.nonWallet)
.and()
.group(
(q) => q
.group(
(q2) => q2
.typeEqualTo(AddressType.frostMS)
.and()
.zSafeFrostEqualTo(true),
)
.or()
.not()
.typeEqualTo(AddressType.frostMS),
)
.sortByDerivationIndex()
.idProperty()
.findAll();

View file

@ -67,6 +67,7 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
final data = Frost.extractDataFromSignConfig(
signConfig: config,
coin: wallet.cryptoCurrency,
serializedKeys: (await wallet.getSerializedKeys())!,
);
final utxos = await ref

View file

@ -69,6 +69,19 @@ class _DesktopAddressListState extends ConsumerState<DesktopAddressList> {
.and()
.not()
.typeEqualTo(AddressType.nonWallet)
.and()
.group(
(q) => q
.group(
(q2) => q2
.typeEqualTo(AddressType.frostMS)
.and()
.zSafeFrostEqualTo(true),
)
.or()
.not()
.typeEqualTo(AddressType.frostMS),
)
.sortByDerivationIndex()
.idProperty()
.findAllSync();
@ -118,6 +131,19 @@ class _DesktopAddressListState extends ConsumerState<DesktopAddressList> {
.and()
.not()
.typeEqualTo(AddressType.nonWallet)
.and()
.group(
(q) => q
.group(
(q2) => q2
.typeEqualTo(AddressType.frostMS)
.and()
.zSafeFrostEqualTo(true),
)
.or()
.not()
.typeEqualTo(AddressType.frostMS),
)
.sortByDerivationIndex()
.idProperty()
.findAllSync();

View file

@ -80,6 +80,7 @@ abstract class Frost {
int feePerWeight,
List<Output> inputs,
}) extractDataFromSignConfig({
required String serializedKeys,
required String signConfig,
required CryptoCurrency coin,
}) {
@ -89,6 +90,7 @@ abstract class Frost {
final signConfigPointer = decodedSignConfig(
encodedConfig: signConfig,
network: network,
serializedKeys: serializedKeys,
);
// get various data from config
@ -126,6 +128,7 @@ abstract class Frost {
final List<Output> outputs = [];
for (int i = 0; i < count; i++) {
final output = signInput(
thresholdKeysWrapperPointer: deserializeKeys(keys: serializedKeys),
signConfig: signConfig,
index: i,
network: network,
@ -283,6 +286,7 @@ abstract class Frost {
//=================== transaction creation ===================================
static String createSignConfig({
required String serializedKeys,
required int network,
required List<
({
@ -297,6 +301,7 @@ abstract class Frost {
}) {
try {
final signConfig = newSignConfig(
thresholdKeysWrapperPointer: deserializeKeys(keys: serializedKeys),
network: network,
outputs: inputs
.map(
@ -402,12 +407,16 @@ abstract class Frost {
}
static Pointer<SignConfig> decodedSignConfig({
required String serializedKeys,
required String encodedConfig,
required int network,
}) {
try {
final configPtr =
decodeSignConfig(encodedSignConfig: encodedConfig, network: network);
final configPtr = decodeSignConfig(
thresholdKeysWrapperPointer: deserializeKeys(keys: serializedKeys),
encodedSignConfig: encodedConfig,
network: network,
);
return configPtr;
} catch (e, s) {
Logging.instance.log(

View file

@ -32,6 +32,8 @@ import '../../models/tx_data.dart';
import '../wallet.dart';
import '../wallet_mixin_interfaces/multi_address_interface.dart';
const kFrostSecureStartingIndex = 1;
class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
with MultiAddressInterface {
BitcoinFrostWallet(CryptoCurrencyNetwork network)
@ -85,8 +87,9 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
final address = await _generateAddress(
change: 0,
index: 0,
index: kFrostSecureStartingIndex,
serializedKeys: serializedKeys,
secure: true,
);
await mainDB.putAddresses([address]);
@ -198,6 +201,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
outputs: txData.recipients!,
changeAddress: changeAddress!.value,
feePerWeight: feePerWeight,
serializedKeys: (await getSerializedKeys())!,
);
} on FrostdartException catch (e) {
if (e.errorCode == NOT_ENOUGH_FUNDS_ERROR &&
@ -375,6 +379,14 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
property: r"subType",
value: AddressSubType.change,
),
const FilterCondition.equalTo(
property: r"zSafeFrost",
value: true,
),
const FilterCondition.greaterThan(
property: r"derivationIndex",
value: 0,
),
],
);
@ -389,6 +401,14 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
property: r"subType",
value: AddressSubType.receiving,
),
const FilterCondition.equalTo(
property: r"zSafeFrost",
value: true,
),
const FilterCondition.greaterThan(
property: r"derivationIndex",
value: 0,
),
],
);
@ -618,13 +638,30 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
if (address == null) {
final serializedKeys = await getSerializedKeys();
if (serializedKeys != null) {
final address = await _generateAddress(
change: 0,
index: 0,
serializedKeys: serializedKeys,
);
int index = kFrostSecureStartingIndex;
const someSaneMaximum = 200;
Address? address;
while (index < someSaneMaximum) {
try {
address = await _generateAddress(
change: 0,
index: index,
serializedKeys: serializedKeys,
secure: true,
);
await mainDB.updateOrPutAddresses([address]);
await mainDB.updateOrPutAddresses([address]);
} catch (_) {}
if (address != null) {
break;
}
index++;
}
if (index >= someSaneMaximum) {
throw Exception(
"index < kFrostSecureStartingIndex hit someSaneMaximum");
}
} else {
Logging.instance.log(
"$runtimeType.checkSaveInitialReceivingAddress() failed due"
@ -818,6 +855,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
_checkGapsLinearly(
serializedKeys,
receiveChain,
secure: true,
),
];
final List<Future<({int index, List<Address> addresses})>>
@ -825,6 +863,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
_checkGapsLinearly(
serializedKeys,
changeChain,
secure: true,
),
];
@ -881,6 +920,8 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
);
await mainDB.updateOrPutAddresses(addressesToStore);
await _legacyInsecureScan(serializedKeys);
});
GlobalEventBus.instance.fire(
@ -908,6 +949,80 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
}
}
// for legacy support secure is set to false to see
// funds received on insecure addresses
Future<void> _legacyInsecureScan(String serializedKeys) async {
const receiveChain = 0;
const changeChain = 1;
final List<Future<({int index, List<Address> addresses})>> receiveFutures =
[
_checkGapsLinearly(
serializedKeys,
receiveChain,
secure: false,
),
];
final List<Future<({int index, List<Address> addresses})>> changeFutures = [
// for legacy support secure is set to false to see
// funds received on insecure addresses
_checkGapsLinearly(
serializedKeys,
changeChain,
secure: false,
),
];
// io limitations may require running these linearly instead
final futuresResult = await Future.wait([
Future.wait(receiveFutures),
Future.wait(changeFutures),
]);
final receiveResults = futuresResult[0];
final changeResults = futuresResult[1];
final List<Address> addressesToStore = [];
int highestReceivingIndexWithHistory = 0;
for (final tuple in receiveResults) {
if (tuple.addresses.isNotEmpty) {
highestReceivingIndexWithHistory = max(
tuple.index,
highestReceivingIndexWithHistory,
);
addressesToStore.addAll(tuple.addresses);
}
}
int highestChangeIndexWithHistory = 0;
for (final tuple in changeResults) {
if (tuple.addresses.isNotEmpty) {
highestChangeIndexWithHistory = max(
tuple.index,
highestChangeIndexWithHistory,
);
addressesToStore.addAll(tuple.addresses);
}
}
// remove extra addresses to help minimize risk of creating a large gap
addressesToStore.removeWhere(
(e) =>
e.subType == AddressSubType.change &&
e.derivationIndex > highestChangeIndexWithHistory,
);
addressesToStore.removeWhere(
(e) =>
e.subType == AddressSubType.receiving &&
e.derivationIndex > highestReceivingIndexWithHistory,
);
if (addressesToStore.isNotEmpty) {
await mainDB.updateOrPutAddresses(addressesToStore);
}
}
@override
Future<void> updateBalance() async {
final utxos = await mainDB.getUTXOs(walletId).findAll();
@ -1394,7 +1509,9 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
@override
Future<void> generateNewChangeAddress() async {
final current = await getCurrentChangeAddress();
int index = current == null ? 0 : current.derivationIndex + 1;
int index = current == null
? kFrostSecureStartingIndex
: current.derivationIndex + 1;
const chain = 1; // change address
final serializedKeys = (await getSerializedKeys())!;
@ -1406,6 +1523,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
change: chain,
index: index,
serializedKeys: serializedKeys,
secure: true,
);
} on FrostdartException catch (e) {
if (e.errorCode == 72) {
@ -1424,7 +1542,9 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
@override
Future<void> generateNewReceivingAddress() async {
final current = await getCurrentReceivingAddress();
int index = current == null ? 0 : current.derivationIndex + 1;
int index = current == null
? kFrostSecureStartingIndex
: current.derivationIndex + 1;
const chain = 0; // receiving address
final serializedKeys = (await getSerializedKeys())!;
@ -1436,6 +1556,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
change: chain,
index: index,
serializedKeys: serializedKeys,
secure: true,
);
} on FrostdartException catch (e) {
if (e.errorCode == 72) {
@ -1532,6 +1653,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
change: chain,
index: startingIndex,
serializedKeys: serializedKeys,
secure: true,
);
} on FrostdartException catch (e) {
if (e.errorCode == 72) {
@ -1554,6 +1676,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
required int change,
required int index,
required String serializedKeys,
required bool secure,
}) async {
final addressDerivationData = (
account: account,
@ -1569,6 +1692,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
: Network.Testnet,
keys: keys,
addressDerivationData: addressDerivationData,
secure: secure,
);
return Address(
@ -1583,16 +1707,18 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
? AddressSubType.change
: AddressSubType.unknown,
type: AddressType.frostMS,
zSafeFrost: secure && index >= kFrostSecureStartingIndex,
);
}
Future<({List<Address> addresses, int index})> _checkGapsLinearly(
String serializedKeys,
int chain,
) async {
int chain, {
required bool secure,
}) async {
final List<Address> addressArray = [];
int gapCounter = 0;
int index = 0;
int index = secure ? kFrostSecureStartingIndex : 0;
for (; gapCounter < 20; index++) {
Logging.instance.log(
"Frost index: $index, \t GapCounter chain=$chain: $gapCounter",
@ -1606,6 +1732,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
change: chain,
index: index,
serializedKeys: serializedKeys,
secure: secure,
);
} on FrostdartException catch (e) {
if (e.errorCode == 72) {
@ -1620,13 +1747,13 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
// get address tx count
final count = await _fetchTxCount(
address: address!,
address: address,
);
// check and add appropriate addresses
if (count > 0) {
// add address to array
addressArray.add(address!);
addressArray.add(address);
// reset counter
gapCounter = 0;
// add info to derivations