address key wif

This commit is contained in:
julian 2024-07-03 15:04:19 -06:00
parent fe5458928f
commit f0b62aed92
4 changed files with 177 additions and 130 deletions

View file

@ -22,6 +22,7 @@ import '../../../utilities/address_utils.dart';
import '../../../utilities/text_styles.dart'; import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart'; import '../../../utilities/util.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../widgets/address_private_key.dart';
import '../../../widgets/background.dart'; import '../../../widgets/background.dart';
import '../../../widgets/conditional_parent.dart'; import '../../../widgets/conditional_parent.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
@ -30,6 +31,7 @@ import '../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../widgets/custom_buttons/simple_edit_button.dart'; import '../../../widgets/custom_buttons/simple_edit_button.dart';
import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../widgets/detail_item.dart';
import '../../../widgets/qr.dart'; import '../../../widgets/qr.dart';
import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/transaction_card.dart'; import '../../../widgets/transaction_card.dart';
@ -298,9 +300,9 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
_Item( DetailItem(
title: "Address", title: "Address",
data: address.value, detail: address.value,
button: isDesktop button: isDesktop
? IconCopyButton( ? IconCopyButton(
data: address.value, data: address.value,
@ -312,9 +314,9 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
const _Div( const _Div(
height: 12, height: 12,
), ),
_Item( DetailItem(
title: "Label", title: "Label",
data: label!.value, detail: label!.value,
button: SimpleEditButton( button: SimpleEditButton(
editValue: label!.value, editValue: label!.value,
editLabel: 'label', editLabel: 'label',
@ -338,9 +340,9 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
height: 12, height: 12,
), ),
if (address.derivationPath != null) if (address.derivationPath != null)
_Item( DetailItem(
title: "Derivation path", title: "Derivation path",
data: address.derivationPath!.value, detail: address.derivationPath!.value,
button: Container(), button: Container(),
), ),
if (address.type == AddressType.spark) if (address.type == AddressType.spark)
@ -348,27 +350,34 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
height: 12, height: 12,
), ),
if (address.type == AddressType.spark) if (address.type == AddressType.spark)
_Item( DetailItem(
title: "Diversifier", title: "Diversifier",
data: address.derivationIndex.toString(), detail: address.derivationIndex.toString(),
button: Container(), button: Container(),
), ),
const _Div( const _Div(
height: 12, height: 12,
), ),
_Item( DetailItem(
title: "Type", title: "Type",
data: address.type.readableName, detail: address.type.readableName,
button: Container(), button: Container(),
), ),
const _Div( const _Div(
height: 12, height: 12,
), ),
_Item( DetailItem(
title: "Sub type", title: "Sub type",
data: address.subType.prettyName, detail: address.subType.prettyName,
button: Container(), button: Container(),
), ),
const _Div(
height: 12,
),
AddressPrivateKey(
walletId: widget.walletId,
address: address,
),
if (!isDesktop) if (!isDesktop)
const SizedBox( const SizedBox(
height: 20, height: 20,
@ -631,64 +640,3 @@ class _Tags extends StatelessWidget {
); );
} }
} }
class _Item extends StatelessWidget {
const _Item({
super.key,
required this.title,
required this.data,
required this.button,
});
final String title;
final String data;
final Widget button;
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: !Util.isDesktop,
builder: (child) => RoundedWhiteContainer(
child: child,
),
child: ConditionalParent(
condition: Util.isDesktop,
builder: (child) => Padding(
padding: const EdgeInsets.all(16),
child: child,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: STextStyles.itemSubtitle(context),
),
button,
],
),
const SizedBox(
height: 5,
),
data.isNotEmpty
? SelectableText(
data,
style: STextStyles.w500_14(context),
)
: Text(
"$title will appear here",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle3,
),
),
],
),
),
);
}
}

View file

@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib; import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
@ -6,6 +8,7 @@ import '../../../models/balance.dart';
import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../utilities/amount/amount.dart'; import '../../../utilities/amount/amount.dart';
import '../../../utilities/enums/derive_path_type_enum.dart'; import '../../../utilities/enums/derive_path_type_enum.dart';
import '../../../utilities/extensions/extensions.dart';
import '../../crypto_currency/intermediate/bip39_hd_currency.dart'; import '../../crypto_currency/intermediate/bip39_hd_currency.dart';
import '../wallet_mixin_interfaces/multi_address_interface.dart'; import '../wallet_mixin_interfaces/multi_address_interface.dart';
import 'bip39_wallet.dart'; import 'bip39_wallet.dart';
@ -28,6 +31,22 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
return coinlib.HDPrivateKey.fromSeed(seed); return coinlib.HDPrivateKey.fromSeed(seed);
} }
Future<String> getPrivateKeyWIF(Address address) async {
final keys =
(await getRootHDNode()).derivePath(address.derivationPath!.value);
final List<int> data = [
cryptoCurrency.networkParams.wifPrefix,
...keys.privateKey.data,
if (keys.privateKey.compressed) 1,
];
final checksum =
coinlib.sha256DoubleHash(Uint8List.fromList(data)).sublist(0, 4);
data.addAll(checksum);
return Uint8List.fromList(data).toBase58Encoded;
}
Future<Address> generateNextReceivingAddress({ Future<Address> generateNextReceivingAddress({
required DerivePathType derivePathType, required DerivePathType derivePathType,
}) async { }) async {

View file

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/isar/models/isar_models.dart';
import '../providers/global/wallets_provider.dart';
import '../utilities/show_loading.dart';
import '../utilities/text_styles.dart';
import '../utilities/util.dart';
import '../wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'custom_buttons/blue_text_button.dart';
import 'detail_item.dart';
class AddressPrivateKey extends ConsumerStatefulWidget {
/// The [walletId] MUST be the id of a [Bip39HDWallet]!
const AddressPrivateKey({
super.key,
required this.walletId,
required this.address,
});
final String walletId;
final Address address;
@override
ConsumerState<AddressPrivateKey> createState() => _AddressPrivateKeyState();
}
class _AddressPrivateKeyState extends ConsumerState<AddressPrivateKey> {
String? _private;
bool _lock = false;
Future<void> _loadPrivKey() async {
// sanity check that should never actually fail in practice.
// Big problems if it actually does though so we check and crash if it fails.
assert(widget.walletId == widget.address.walletId);
if (_lock) {
return;
}
_lock = true;
try {
final wallet =
ref.read(pWallets).getWallet(widget.walletId) as Bip39HDWallet;
_private = await showLoading(
whileFuture: wallet.getPrivateKeyWIF(widget.address),
context: context,
message: "Loading...",
delay: const Duration(milliseconds: 800),
rootNavigator: Util.isDesktop,
);
if (context.mounted) {
setState(() {});
} else {
_private == null;
}
} finally {
_lock = false;
}
}
@override
Widget build(BuildContext context) {
return DetailItemBase(
button: CustomTextButton(
text: "Show",
onTap: _loadPrivKey,
enabled: _private == null,
),
title: Text(
"Private key",
style: STextStyles.itemSubtitle(context),
),
detail: SelectableText(
_private ?? "*" * 52, // 52 is approx length
style: STextStyles.w500_14(context),
),
);
}
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../themes/stack_colors.dart'; import '../themes/stack_colors.dart';
import '../utilities/text_styles.dart'; import '../utilities/text_styles.dart';
import '../utilities/util.dart'; import '../utilities/util.dart';
@ -27,15 +28,61 @@ class DetailItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TextStyle detailStyle; TextStyle detailStyle = STextStyles.w500_14(context);
String _detail = detail;
if (overrideDetailTextColor != null) { if (overrideDetailTextColor != null) {
detailStyle = STextStyles.w500_14(context).copyWith( detailStyle = STextStyles.w500_14(context).copyWith(
color: overrideDetailTextColor, color: overrideDetailTextColor,
); );
} else {
detailStyle = STextStyles.w500_14(context);
} }
if (detail.isEmpty && showEmptyDetail) {
_detail = "$title will appear here";
detailStyle = detailStyle.copyWith(
color: Theme.of(context).extension<StackColors>()!.textSubtitle3,
);
}
return DetailItemBase(
horizontal: horizontal,
title: disableSelectableText
? Text(
title,
style: STextStyles.itemSubtitle(context),
)
: SelectableText(
title,
style: STextStyles.itemSubtitle(context),
),
detail: disableSelectableText
? Text(
_detail,
style: detailStyle,
)
: SelectableText(
_detail,
style: detailStyle,
),
);
}
}
class DetailItemBase extends StatelessWidget {
const DetailItemBase({
super.key,
required this.title,
required this.detail,
this.button,
this.horizontal = false,
});
final Widget title;
final Widget detail;
final Widget? button;
final bool horizontal;
@override
Widget build(BuildContext context) {
return ConditionalParent( return ConditionalParent(
condition: !Util.isDesktop, condition: !Util.isDesktop,
builder: (child) => RoundedWhiteContainer( builder: (child) => RoundedWhiteContainer(
@ -51,24 +98,8 @@ class DetailItem extends StatelessWidget {
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
disableSelectableText
? Text(
title, title,
style: STextStyles.itemSubtitle(context),
)
: SelectableText(
title,
style: STextStyles.itemSubtitle(context),
),
disableSelectableText
? Text(
detail, detail,
style: detailStyle,
)
: SelectableText(
detail,
style: detailStyle,
),
], ],
) )
: Column( : Column(
@ -77,48 +108,14 @@ class DetailItem extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
disableSelectableText
? Text(
title, title,
style: STextStyles.itemSubtitle(context),
)
: SelectableText(
title,
style: STextStyles.itemSubtitle(context),
),
button ?? Container(), button ?? Container(),
], ],
), ),
const SizedBox( const SizedBox(
height: 5, height: 5,
), ),
detail.isEmpty && showEmptyDetail
? disableSelectableText
? Text(
"$title will appear here",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle3,
),
)
: SelectableText(
"$title will appear here",
style: STextStyles.w500_14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle3,
),
)
: disableSelectableText
? Text(
detail, detail,
style: detailStyle,
)
: SelectableText(
detail,
style: detailStyle,
),
], ],
), ),
), ),