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:
@@ -110,6 +110,18 @@ public sealed class Loc
|
|||||||
"Achtung: ohne Verschlüsselung bleibt der Seed im Klartext auf der Festplatte."],
|
"Achtung: ohne Verschlüsselung bleibt der Seed im Klartext auf der Festplatte."],
|
||||||
["wiz.back"] = ["Indietro", "Back", "Atrás", "Retour", "Voltar", "Zurück"],
|
["wiz.back"] = ["Indietro", "Back", "Atrás", "Retour", "Voltar", "Zurück"],
|
||||||
["wiz.next"] = ["Avanti", "Next", "Siguiente", "Suivant", "Próximo", "Weiter"],
|
["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
|
// Pannello wallet
|
||||||
["wallet.close"] = ["Chiudi wallet", "Close wallet", "Cerrar wallet", "Fermer le wallet", "Fechar carteira", "Wallet schließen"],
|
["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 StepConfirmSeed = "confirm-seed";
|
||||||
public const string StepWords = "words";
|
public const string StepWords = "words";
|
||||||
public const string StepPassphrase = "passphrase";
|
public const string StepPassphrase = "passphrase";
|
||||||
|
public const string StepScriptType = "script-type";
|
||||||
public const string StepPassword = "password";
|
public const string StepPassword = "password";
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -34,18 +35,37 @@ public partial class MainWindowViewModel
|
|||||||
[NotifyPropertyChangedFor(nameof(IsStepConfirmSeed))]
|
[NotifyPropertyChangedFor(nameof(IsStepConfirmSeed))]
|
||||||
[NotifyPropertyChangedFor(nameof(IsStepWords))]
|
[NotifyPropertyChangedFor(nameof(IsStepWords))]
|
||||||
[NotifyPropertyChangedFor(nameof(IsStepPassphrase))]
|
[NotifyPropertyChangedFor(nameof(IsStepPassphrase))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepScriptType))]
|
||||||
[NotifyPropertyChangedFor(nameof(IsStepPassword))]
|
[NotifyPropertyChangedFor(nameof(IsStepPassword))]
|
||||||
private string setupStep = StepStart;
|
private string setupStep = StepStart;
|
||||||
|
|
||||||
public bool IsStepDataLocation => SetupStep == StepDataLocation;
|
public bool IsStepDataLocation => SetupStep == StepDataLocation;
|
||||||
public bool IsStepStart => SetupStep == StepStart;
|
public bool IsStepStart => SetupStep == StepStart;
|
||||||
public bool IsStepChooseWallet => SetupStep == StepChooseWallet;
|
public bool IsStepChooseWallet => SetupStep == StepChooseWallet;
|
||||||
public bool IsStepOpen => SetupStep == StepOpen;
|
public bool IsStepOpen => SetupStep == StepOpen;
|
||||||
public bool IsStepShowSeed => SetupStep == StepShowSeed;
|
public bool IsStepShowSeed => SetupStep == StepShowSeed;
|
||||||
public bool IsStepConfirmSeed => SetupStep == StepConfirmSeed;
|
public bool IsStepConfirmSeed => SetupStep == StepConfirmSeed;
|
||||||
public bool IsStepWords => SetupStep == StepWords;
|
public bool IsStepWords => SetupStep == StepWords;
|
||||||
public bool IsStepPassphrase => SetupStep == StepPassphrase;
|
public bool IsStepPassphrase => SetupStep == StepPassphrase;
|
||||||
public bool IsStepPassword => SetupStep == StepPassword;
|
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();
|
public string DefaultDataPath => AppPaths.DefaultDataRoot();
|
||||||
|
|
||||||
@@ -96,6 +116,7 @@ public partial class MainWindowViewModel
|
|||||||
private void RefreshSetupState()
|
private void RefreshSetupState()
|
||||||
{
|
{
|
||||||
SetupStep = StepStart;
|
SetupStep = StepStart;
|
||||||
|
SelectedScriptKind = ScriptKind.NativeSegwit;
|
||||||
MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = ConfirmPasswordInput = "";
|
MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = ConfirmPasswordInput = "";
|
||||||
WalletFileExists = AppPaths.WalletFiles(Net).Count > 0;
|
WalletFileExists = AppPaths.WalletFiles(Net).Count > 0;
|
||||||
StatusMessage = "";
|
StatusMessage = "";
|
||||||
@@ -192,6 +213,13 @@ public partial class MainWindowViewModel
|
|||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void WizardNextFromPassphrase()
|
private void WizardNextFromPassphrase()
|
||||||
|
{
|
||||||
|
SetupStep = StepScriptType;
|
||||||
|
StatusMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardNextFromScriptType()
|
||||||
{
|
{
|
||||||
PasswordInput = ConfirmPasswordInput = "";
|
PasswordInput = ConfirmPasswordInput = "";
|
||||||
EncryptWallet = true;
|
EncryptWallet = true;
|
||||||
@@ -208,7 +236,8 @@ public partial class MainWindowViewModel
|
|||||||
StepChooseWallet or StepShowSeed or StepWords => StepStart,
|
StepChooseWallet or StepShowSeed or StepWords => StepStart,
|
||||||
StepConfirmSeed => StepShowSeed,
|
StepConfirmSeed => StepShowSeed,
|
||||||
StepPassphrase => _isRestoreFlow ? StepWords : StepConfirmSeed,
|
StepPassphrase => _isRestoreFlow ? StepWords : StepConfirmSeed,
|
||||||
StepPassword => StepPassphrase,
|
StepScriptType => StepPassphrase,
|
||||||
|
StepPassword => StepScriptType,
|
||||||
_ => StepStart,
|
_ => StepStart,
|
||||||
};
|
};
|
||||||
if (SetupStep == StepStart)
|
if (SetupStep == StepStart)
|
||||||
@@ -243,7 +272,7 @@ public partial class MainWindowViewModel
|
|||||||
var (doc, account) = WalletLoader.NewFromMnemonic(
|
var (doc, account) = WalletLoader.NewFromMnemonic(
|
||||||
MnemonicInput,
|
MnemonicInput,
|
||||||
string.IsNullOrEmpty(PassphraseInput) ? null : PassphraseInput,
|
string.IsNullOrEmpty(PassphraseInput) ? null : PassphraseInput,
|
||||||
ScriptKind.NativeSegwit,
|
SelectedScriptKind,
|
||||||
Profile);
|
Profile);
|
||||||
var path = AppPaths.DefaultWalletPath(Net);
|
var path = AppPaths.DefaultWalletPath(Net);
|
||||||
for (var n = 2; WalletStore.Exists(path); n++)
|
for (var n = 2; WalletStore.Exists(path); n++)
|
||||||
|
|||||||
@@ -171,6 +171,54 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</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) -->
|
<!-- Passo finale: password del file (cifratura, stile Electrum) -->
|
||||||
<StackPanel IsVisible="{Binding IsStepPassword}" Spacing="12">
|
<StackPanel IsVisible="{Binding IsStepPassword}" Spacing="12">
|
||||||
<TextBlock Text="{Binding Loc[wiz.password.title]}" FontSize="18" FontWeight="Bold"/>
|
<TextBlock Text="{Binding Loc[wiz.password.title]}" FontSize="18" FontWeight="Bold"/>
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ public enum ScriptKind
|
|||||||
|
|
||||||
/// <summary>P2WSH (multisig native).</summary>
|
/// <summary>P2WSH (multisig native).</summary>
|
||||||
NativeSegwitMultisig,
|
NativeSegwitMultisig,
|
||||||
|
|
||||||
|
/// <summary>P2TR key-path only (Taproot, BIP86) — witness v1, bech32m.</summary>
|
||||||
|
Taproot,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public static class ChainProfiles
|
|||||||
[ScriptKind.WrappedSegwitMultisig] = new(0x0295b005, 0x0295b43f), // Yprv / Ypub
|
[ScriptKind.WrappedSegwitMultisig] = new(0x0295b005, 0x0295b43f), // Yprv / Ypub
|
||||||
[ScriptKind.NativeSegwit] = new(0x04b2430c, 0x04b24746), // zprv / zpub
|
[ScriptKind.NativeSegwit] = new(0x04b2430c, 0x04b24746), // zprv / zpub
|
||||||
[ScriptKind.NativeSegwitMultisig] = new(0x02aa7a99, 0x02aa7ed3), // Zprv / Zpub
|
[ScriptKind.NativeSegwitMultisig] = new(0x02aa7a99, 0x02aa7ed3), // Zprv / Zpub
|
||||||
|
// Nessun SLIP-132 per P2TR: il contesto è dato dal path m/86'/…
|
||||||
|
[ScriptKind.Taproot] = new(0x0488ade4, 0x0488b21e), // xprv / xpub (BIP32 standard)
|
||||||
},
|
},
|
||||||
// Server di indicizzazione noti per il primo contatto (§3/§9); altri
|
// Server di indicizzazione noti per il primo contatto (§3/§9); altri
|
||||||
// peer vengono scoperti via server.peers.subscribe.
|
// peer vengono scoperti via server.peers.subscribe.
|
||||||
@@ -70,6 +72,7 @@ public static class ChainProfiles
|
|||||||
[ScriptKind.WrappedSegwitMultisig] = new(0x024285b5, 0x024289ef), // Uprv / Upub
|
[ScriptKind.WrappedSegwitMultisig] = new(0x024285b5, 0x024289ef), // Uprv / Upub
|
||||||
[ScriptKind.NativeSegwit] = new(0x045f18bc, 0x045f1cf6), // vprv / vpub
|
[ScriptKind.NativeSegwit] = new(0x045f18bc, 0x045f1cf6), // vprv / vpub
|
||||||
[ScriptKind.NativeSegwitMultisig] = new(0x02575048, 0x02575483), // Vprv / Vpub
|
[ScriptKind.NativeSegwitMultisig] = new(0x02575048, 0x02575483), // Vprv / Vpub
|
||||||
|
[ScriptKind.Taproot] = new(0x04358394, 0x043587cf), // tprv / tpub (BIP32 standard)
|
||||||
},
|
},
|
||||||
BootstrapServers = [],
|
BootstrapServers = [],
|
||||||
Checkpoints = [],
|
Checkpoints = [],
|
||||||
|
|||||||
@@ -22,12 +22,16 @@ public static class DerivationPaths
|
|||||||
/// <summary>Purpose BIP48 (multisig — fase successiva, §16 passo 8).</summary>
|
/// <summary>Purpose BIP48 (multisig — fase successiva, §16 passo 8).</summary>
|
||||||
public const int PurposeMultisig = 48;
|
public const int PurposeMultisig = 48;
|
||||||
|
|
||||||
|
/// <summary>Purpose BIP86 (P2TR key-path, Taproot).</summary>
|
||||||
|
public const int PurposeTaproot = 86;
|
||||||
|
|
||||||
public static int PurposeFor(ScriptKind kind) => kind switch
|
public static int PurposeFor(ScriptKind kind) => kind switch
|
||||||
{
|
{
|
||||||
ScriptKind.Legacy => PurposeLegacy,
|
ScriptKind.Legacy => PurposeLegacy,
|
||||||
ScriptKind.WrappedSegwit => PurposeWrappedSegwit,
|
ScriptKind.WrappedSegwit => PurposeWrappedSegwit,
|
||||||
ScriptKind.NativeSegwit => PurposeNativeSegwit,
|
ScriptKind.NativeSegwit => PurposeNativeSegwit,
|
||||||
ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig => PurposeMultisig,
|
ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig => PurposeMultisig,
|
||||||
|
ScriptKind.Taproot => PurposeTaproot,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ public static class DerivationPaths
|
|||||||
ScriptKind.Legacy => ScriptPubKeyType.Legacy,
|
ScriptKind.Legacy => ScriptPubKeyType.Legacy,
|
||||||
ScriptKind.WrappedSegwit => ScriptPubKeyType.SegwitP2SH,
|
ScriptKind.WrappedSegwit => ScriptPubKeyType.SegwitP2SH,
|
||||||
ScriptKind.NativeSegwit => ScriptPubKeyType.Segwit,
|
ScriptKind.NativeSegwit => ScriptPubKeyType.Segwit,
|
||||||
|
ScriptKind.Taproot => ScriptPubKeyType.TaprootBIP86,
|
||||||
ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig =>
|
ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig =>
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
"I tipi multisig derivano da redeem script: supporto previsto con i wallet M-di-N (§4.5)."),
|
"I tipi multisig derivano da redeem script: supporto previsto con i wallet M-di-N (§4.5)."),
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using NBitcoin;
|
||||||
|
using PalladiumWallet.Core.Chain;
|
||||||
|
using PalladiumWallet.Core.Crypto;
|
||||||
|
|
||||||
|
namespace PalladiumWallet.Tests.Crypto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Vettori di test BIP86 (mnemonica abandon-about, senza passphrase).
|
||||||
|
/// La chiave pubblica tweakizzata (output key, 32 byte x-only) è chain-independent:
|
||||||
|
/// viene verificata contro i vettori ufficiali Bitcoin, poi si controlla che
|
||||||
|
/// l'indirizzo PLM abbia il prefisso plm1p (witness v1, bech32m).
|
||||||
|
/// Il path m/86'/0'/0' usa coin_type=0 (non 746) per aderire ai vettori BIP86.
|
||||||
|
/// </summary>
|
||||||
|
public class Bip86TaprootTests
|
||||||
|
{
|
||||||
|
private static HdAccount Account()
|
||||||
|
{
|
||||||
|
Assert.True(Bip39.TryParse(
|
||||||
|
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||||
|
out var mnemonic));
|
||||||
|
return HdAccount.FromSeed(
|
||||||
|
Bip39.ToSeed(mnemonic!),
|
||||||
|
ScriptKind.Taproot,
|
||||||
|
ChainProfiles.Mainnet,
|
||||||
|
new KeyPath("86'/0'/0'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Il_derivation_path_di_un_account_taproot_usa_purpose_86()
|
||||||
|
{
|
||||||
|
var path = DerivationPaths.AccountPath(ScriptKind.Taproot, ChainProfiles.Mainnet, 0);
|
||||||
|
Assert.Equal($"86'/{ChainProfiles.Mainnet.Bip44CoinType}'/0'", path.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gli_indirizzi_plm_taproot_iniziano_con_plm1p()
|
||||||
|
{
|
||||||
|
var account = Account();
|
||||||
|
var addr0 = account.GetReceiveAddress(0).ToString();
|
||||||
|
var addr1 = account.GetReceiveAddress(1).ToString();
|
||||||
|
var change0 = account.GetChangeAddress(0).ToString();
|
||||||
|
|
||||||
|
Assert.StartsWith("plm1p", addr0);
|
||||||
|
Assert.StartsWith("plm1p", addr1);
|
||||||
|
Assert.StartsWith("plm1p", change0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// L'output key (chiave tweakizzata x-only, 32 byte) è identica al vettore BIP86
|
||||||
|
/// indipendentemente dalla rete: la rete cambia solo HRP e checksum, non il programma.
|
||||||
|
/// Indirizzi Bitcoin da https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false, 0, "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr")]
|
||||||
|
[InlineData(false, 1, "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh")]
|
||||||
|
[InlineData(true, 0, "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7")]
|
||||||
|
public void L_output_key_coincide_col_vettore_bip86(
|
||||||
|
bool isChange, int index, string bitcoinAddress)
|
||||||
|
{
|
||||||
|
var account = Account();
|
||||||
|
var plmAddr = account.GetAddress(isChange, index);
|
||||||
|
|
||||||
|
// Il witness program (output key tweakizzata) è chain-independent
|
||||||
|
var btcAddr = (TaprootAddress)BitcoinAddress.Create(bitcoinAddress, Network.Main);
|
||||||
|
var plmTaproot = (TaprootAddress)plmAddr;
|
||||||
|
Assert.Equal(btcAddr.PubKey, plmTaproot.PubKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Il_wallet_taproot_e_watch_only_se_creato_da_xpub()
|
||||||
|
{
|
||||||
|
var full = Account();
|
||||||
|
var watchOnly = HdAccount.FromAccountXpub(
|
||||||
|
full.AccountXpub, ScriptKind.Taproot, ChainProfiles.Mainnet);
|
||||||
|
Assert.True(watchOnly.IsWatchOnly);
|
||||||
|
Assert.Equal(
|
||||||
|
full.GetReceiveAddress(0).ToString(),
|
||||||
|
watchOnly.GetReceiveAddress(0).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void I_tipi_multisig_lanciano_not_supported()
|
||||||
|
{
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
|
() => DerivationPaths.ScriptPubKeyTypeFor(ScriptKind.WrappedSegwitMultisig));
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
|
() => DerivationPaths.ScriptPubKeyTypeFor(ScriptKind.NativeSegwitMultisig));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user