feat(wallet): add P2PKH, P2SH-P2WPKH, and P2TR (Taproot/BIP86) address types

- ScriptKind.Taproot (BIP86, purpose 86', P2WPKH → witness v1 bech32m plm1p)
  derivation wired in DerivationPaths and ChainProfiles (xprv/xpub headers,
  no SLIP-132 standard for P2TR); NBitcoin ScriptPubKeyType.TaprootBIP86
- Wizard step StepScriptType inserted between passphrase and password: 4 radio
  buttons (Legacy / Wrapped SegWit / Native SegWit★ / Taproot), localized in
  6 languages; CreateOrRestore now uses SelectedScriptKind instead of hardcoded
  NativeSegwit; back navigation updated accordingly
- BIP86 golden-vector tests (Bip86TaprootTests): output key verified against
  official Bitcoin BIP86 addresses, plm1p prefix asserted
This commit is contained in:
2026-06-15 12:03:46 +02:00
parent 8b960458ee
commit 002c854497
7 changed files with 198 additions and 9 deletions
+12
View File
@@ -110,6 +110,18 @@ public sealed class Loc
"Achtung: ohne Verschlüsselung bleibt der Seed im Klartext auf der Festplatte."],
["wiz.back"] = ["Indietro", "Back", "Atrás", "Retour", "Voltar", "Zurück"],
["wiz.next"] = ["Avanti", "Next", "Siguiente", "Suivant", "Próximo", "Weiter"],
["wiz.scripttype.title"] = ["Tipo di script e indirizzi", "Script type and addresses", "Tipo de script y direcciones", "Type de script et adresses", "Tipo de script e endereços", "Skripttyp und Adressen"],
["wiz.scripttype.hint"] = [
"Determina il formato degli indirizzi. Se non sai cosa scegliere, usa Native SegWit.",
"Determines the address format. If unsure, use Native SegWit.",
"Determina el formato de los direcciones. Si no sabes, usa Native SegWit.",
"Détermine le format des adresses. En cas de doute, utilisez Native SegWit.",
"Determina o formato dos endereços. Em caso de dúvida, use Native SegWit.",
"Bestimmt das Adressformat. Wenn Sie unsicher sind, verwenden Sie Native SegWit."],
["wiz.scripttype.legacy.desc"] = ["BIP44 · m/44'/… · indirizzi P", "BIP44 · m/44'/… · P addresses", "BIP44 · m/44'/… · direcciones P", "BIP44 · m/44'/… · adresses P", "BIP44 · m/44'/… · endereços P", "BIP44 · m/44'/… · P-Adressen"],
["wiz.scripttype.wrapped.desc"] = ["BIP49 · m/49'/… · indirizzi 3", "BIP49 · m/49'/… · 3 addresses", "BIP49 · m/49'/… · direcciones 3", "BIP49 · m/49'/… · adresses 3", "BIP49 · m/49'/… · endereços 3", "BIP49 · m/49'/… · 3-Adressen"],
["wiz.scripttype.native.desc"] = ["BIP84 · m/84'/… · indirizzi plm1q — consigliato", "BIP84 · m/84'/… · plm1q addresses — recommended", "BIP84 · m/84'/… · direcciones plm1q — recomendado", "BIP84 · m/84'/… · adresses plm1q — recommandé", "BIP84 · m/84'/… · endereços plm1q — recomendado", "BIP84 · m/84'/… · plm1q-Adressen — empfohlen"],
["wiz.scripttype.taproot.desc"] = ["BIP86 · m/86'/… · indirizzi plm1p", "BIP86 · m/86'/… · plm1p addresses", "BIP86 · m/86'/… · direcciones plm1p", "BIP86 · m/86'/… · adresses plm1p", "BIP86 · m/86'/… · endereços plm1p", "BIP86 · m/86'/… · plm1p-Adressen"],
// Pannello wallet
["wallet.close"] = ["Chiudi wallet", "Close wallet", "Cerrar wallet", "Fermer le wallet", "Fechar carteira", "Wallet schließen"],
@@ -23,6 +23,7 @@ public partial class MainWindowViewModel
public const string StepConfirmSeed = "confirm-seed";
public const string StepWords = "words";
public const string StepPassphrase = "passphrase";
public const string StepScriptType = "script-type";
public const string StepPassword = "password";
[ObservableProperty]
@@ -34,18 +35,37 @@ public partial class MainWindowViewModel
[NotifyPropertyChangedFor(nameof(IsStepConfirmSeed))]
[NotifyPropertyChangedFor(nameof(IsStepWords))]
[NotifyPropertyChangedFor(nameof(IsStepPassphrase))]
[NotifyPropertyChangedFor(nameof(IsStepScriptType))]
[NotifyPropertyChangedFor(nameof(IsStepPassword))]
private string setupStep = StepStart;
public bool IsStepDataLocation => SetupStep == StepDataLocation;
public bool IsStepStart => SetupStep == StepStart;
public bool IsStepStart => SetupStep == StepStart;
public bool IsStepChooseWallet => SetupStep == StepChooseWallet;
public bool IsStepOpen => SetupStep == StepOpen;
public bool IsStepShowSeed => SetupStep == StepShowSeed;
public bool IsStepConfirmSeed => SetupStep == StepConfirmSeed;
public bool IsStepWords => SetupStep == StepWords;
public bool IsStepPassphrase => SetupStep == StepPassphrase;
public bool IsStepPassword => SetupStep == StepPassword;
public bool IsStepOpen => SetupStep == StepOpen;
public bool IsStepShowSeed => SetupStep == StepShowSeed;
public bool IsStepConfirmSeed => SetupStep == StepConfirmSeed;
public bool IsStepWords => SetupStep == StepWords;
public bool IsStepPassphrase => SetupStep == StepPassphrase;
public bool IsStepScriptType => SetupStep == StepScriptType;
public bool IsStepPassword => SetupStep == StepPassword;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsLegacySelected))]
[NotifyPropertyChangedFor(nameof(IsWrappedSegwitSelected))]
[NotifyPropertyChangedFor(nameof(IsNativeSegwitSelected))]
[NotifyPropertyChangedFor(nameof(IsTaprootSelected))]
private ScriptKind selectedScriptKind = ScriptKind.NativeSegwit;
public bool IsLegacySelected => SelectedScriptKind == ScriptKind.Legacy;
public bool IsWrappedSegwitSelected => SelectedScriptKind == ScriptKind.WrappedSegwit;
public bool IsNativeSegwitSelected => SelectedScriptKind == ScriptKind.NativeSegwit;
public bool IsTaprootSelected => SelectedScriptKind == ScriptKind.Taproot;
[RelayCommand] private void SelectLegacy() => SelectedScriptKind = ScriptKind.Legacy;
[RelayCommand] private void SelectWrappedSegwit() => SelectedScriptKind = ScriptKind.WrappedSegwit;
[RelayCommand] private void SelectNativeSegwit() => SelectedScriptKind = ScriptKind.NativeSegwit;
[RelayCommand] private void SelectTaproot() => SelectedScriptKind = ScriptKind.Taproot;
public string DefaultDataPath => AppPaths.DefaultDataRoot();
@@ -96,6 +116,7 @@ public partial class MainWindowViewModel
private void RefreshSetupState()
{
SetupStep = StepStart;
SelectedScriptKind = ScriptKind.NativeSegwit;
MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = ConfirmPasswordInput = "";
WalletFileExists = AppPaths.WalletFiles(Net).Count > 0;
StatusMessage = "";
@@ -192,6 +213,13 @@ public partial class MainWindowViewModel
[RelayCommand]
private void WizardNextFromPassphrase()
{
SetupStep = StepScriptType;
StatusMessage = "";
}
[RelayCommand]
private void WizardNextFromScriptType()
{
PasswordInput = ConfirmPasswordInput = "";
EncryptWallet = true;
@@ -208,7 +236,8 @@ public partial class MainWindowViewModel
StepChooseWallet or StepShowSeed or StepWords => StepStart,
StepConfirmSeed => StepShowSeed,
StepPassphrase => _isRestoreFlow ? StepWords : StepConfirmSeed,
StepPassword => StepPassphrase,
StepScriptType => StepPassphrase,
StepPassword => StepScriptType,
_ => StepStart,
};
if (SetupStep == StepStart)
@@ -243,7 +272,7 @@ public partial class MainWindowViewModel
var (doc, account) = WalletLoader.NewFromMnemonic(
MnemonicInput,
string.IsNullOrEmpty(PassphraseInput) ? null : PassphraseInput,
ScriptKind.NativeSegwit,
SelectedScriptKind,
Profile);
var path = AppPaths.DefaultWalletPath(Net);
for (var n = 2; WalletStore.Exists(path); n++)
+48
View File
@@ -171,6 +171,54 @@
</StackPanel>
</StackPanel>
<!-- Passo: scelta del tipo di script/indirizzi -->
<StackPanel IsVisible="{Binding IsStepScriptType}" Spacing="12">
<TextBlock Text="{Binding Loc[wiz.scripttype.title]}" FontSize="18" FontWeight="Bold"/>
<TextBlock Text="{Binding Loc[wiz.scripttype.hint]}" TextWrapping="Wrap" Foreground="Gray"/>
<RadioButton GroupName="ScriptType"
IsChecked="{Binding IsLegacySelected, Mode=OneWay}"
Command="{Binding SelectLegacyCommand}">
<StackPanel Spacing="2">
<TextBlock Text="Legacy (P2PKH)" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Loc[wiz.scripttype.legacy.desc]}" Foreground="Gray" FontSize="12"/>
</StackPanel>
</RadioButton>
<RadioButton GroupName="ScriptType"
IsChecked="{Binding IsWrappedSegwitSelected, Mode=OneWay}"
Command="{Binding SelectWrappedSegwitCommand}">
<StackPanel Spacing="2">
<TextBlock Text="Wrapped SegWit (P2SH-P2WPKH)" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Loc[wiz.scripttype.wrapped.desc]}" Foreground="Gray" FontSize="12"/>
</StackPanel>
</RadioButton>
<RadioButton GroupName="ScriptType"
IsChecked="{Binding IsNativeSegwitSelected, Mode=OneWay}"
Command="{Binding SelectNativeSegwitCommand}">
<StackPanel Spacing="2">
<TextBlock Text="Native SegWit (P2WPKH)" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Loc[wiz.scripttype.native.desc]}" Foreground="Gray" FontSize="12"/>
</StackPanel>
</RadioButton>
<RadioButton GroupName="ScriptType"
IsChecked="{Binding IsTaprootSelected, Mode=OneWay}"
Command="{Binding SelectTaprootCommand}">
<StackPanel Spacing="2">
<TextBlock Text="Taproot (P2TR)" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Loc[wiz.scripttype.taproot.desc]}" Foreground="Gray" FontSize="12"/>
</StackPanel>
</RadioButton>
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Content="{Binding Loc[wiz.back]}" Command="{Binding WizardBackCommand}"/>
<Button Content="{Binding Loc[wiz.next]}" Classes="accent"
Command="{Binding WizardNextFromScriptTypeCommand}"/>
</StackPanel>
</StackPanel>
<!-- Passo finale: password del file (cifratura, stile Electrum) -->
<StackPanel IsVisible="{Binding IsStepPassword}" Spacing="12">
<TextBlock Text="{Binding Loc[wiz.password.title]}" FontSize="18" FontWeight="Bold"/>