docs: translate all code comments to English (language policy)

Translate every Italian /// XML doc comment, <!-- --> XAML comment, and
// inline comment to English across all source files (Core, App, tests).
Add the language policy to CLAUDE.md (conversation Italian; all code
and docs English). Update one test assertion that checked an Italian
exception message that was also translated.
This commit is contained in:
2026-06-16 14:40:06 +02:00
parent 4b82a0852c
commit 3d5a226a5a
59 changed files with 721 additions and 716 deletions
+21 -21
View File
@@ -3,8 +3,8 @@ using NBitcoin;
namespace PalladiumWallet.Core.Crypto;
/// <summary>
/// Lingue wordlist BIP39 supportate (blueprint §4.1). Le liste sono incorporate
/// in NBitcoin: nessun accesso a rete o filesystem.
/// Supported BIP39 wordlist languages (blueprint §4.1). The lists are embedded
/// in NBitcoin: no network or filesystem access required.
/// </summary>
public enum MnemonicLanguage
{
@@ -18,7 +18,7 @@ public enum MnemonicLanguage
Czech,
}
/// <summary>Numero di parole supportato in creazione (blueprint §4.1: 12 o 24).</summary>
/// <summary>Supported word counts for mnemonic creation (blueprint §4.1: 12 or 24).</summary>
public enum MnemonicLength
{
Twelve = 12,
@@ -26,16 +26,16 @@ public enum MnemonicLength
}
/// <summary>
/// Facciata BIP39 su NBitcoin.Mnemonic (blueprint §4.1). NBitcoin copre già
/// entropia→parole, checksum, normalizzazione NFKD e PBKDF2-HMAC-SHA512 a 2048
/// round con salt "mnemonic"+passphrase: qui si restringe l'API ai casi del
/// blueprint e si centralizza la mappa delle lingue. Quando arriverà il seed
/// nativo versionato (§4.1 punto 1), il riconoscimento multi-schema sarà una
/// catena di TryParse per schema.
/// BIP39 facade over NBitcoin.Mnemonic (blueprint §4.1). NBitcoin already handles
/// entropy→words, checksum, NFKD normalisation, and PBKDF2-HMAC-SHA512 with 2048
/// rounds using salt "mnemonic"+passphrase. This class narrows the API to the
/// blueprint use-cases and centralises the language map. When the native versioned
/// seed arrives (§4.1 point 1), multi-scheme recognition will be a chain of
/// TryParse calls per scheme.
/// </summary>
public static class Bip39
{
/// <summary>Genera una nuova mnemonica con entropia dal CSPRNG di sistema.</summary>
/// <summary>Generates a new mnemonic using entropy from the system CSPRNG.</summary>
public static Mnemonic Generate(MnemonicLength length, MnemonicLanguage language = MnemonicLanguage.English)
{
var wordCount = length == MnemonicLength.Twelve ? WordCount.Twelve : WordCount.TwentyFour;
@@ -43,10 +43,10 @@ public static class Bip39
}
/// <summary>
/// Riconosce e valida una mnemonica BIP39: numero di parole, appartenenza alla
/// wordlist e checksum. Se <paramref name="language"/> è indicata viene provata
/// per prima; altrimenti la lingua è auto-rilevata (parole condivise tra liste
/// possono rendere ambiguo l'autodetect: in import l'utente può forzarla).
/// Recognises and validates a BIP39 mnemonic: word count, wordlist membership,
/// and checksum. If <paramref name="language"/> is specified it is tried first;
/// otherwise the language is auto-detected (words shared between lists can make
/// autodetect ambiguous — the user can override it on import).
/// </summary>
public static bool TryParse(string text, out Mnemonic? mnemonic, MnemonicLanguage? language = null)
{
@@ -54,8 +54,8 @@ public static class Bip39
if (string.IsNullOrWhiteSpace(text))
return false;
// Solo trim esterno: la normalizzazione NFKD e lo spazio ideografico
// giapponese li gestisce NBitcoin, il testo non va alterato (§4.1).
// Outer trim only: NBitcoin handles NFKD normalisation and Japanese
// ideographic spaces — the text must not be altered further (§4.1).
text = text.Trim();
IEnumerable<Wordlist> candidates = language is not null
@@ -67,7 +67,7 @@ public static class Bip39
try
{
var parsed = new Mnemonic(text, wordlist);
// Il costruttore NON verifica il checksum: controllo esplicito obbligatorio.
// The constructor does NOT verify the checksum explicit check is mandatory.
if (parsed.IsValidChecksum && parsed.Words.Length is 12 or 15 or 18 or 21 or 24)
{
mnemonic = parsed;
@@ -76,7 +76,7 @@ public static class Bip39
}
catch (FormatException)
{
// Parole fuori wordlist o conteggio non valido: si prova oltre.
// Words outside the wordlist or invalid count — try the next candidate.
}
}
@@ -84,9 +84,9 @@ public static class Bip39
}
/// <summary>
/// Mnemonica seed di 64 byte (PBKDF2-HMAC-SHA512, 2048 round, salt
/// "mnemonic"+passphrase). La passphrase cambia completamente il wallet
/// derivato: avvisi UI obbligatori (§4.1).
/// Mnemonic → 64-byte seed (PBKDF2-HMAC-SHA512, 2048 rounds, salt
/// "mnemonic"+passphrase). The passphrase completely changes the derived
/// wallet — mandatory UI warnings required (§4.1).
/// </summary>
public static byte[] ToSeed(Mnemonic mnemonic, string? passphrase = null) =>
mnemonic.DeriveSeed(passphrase);
+17 -17
View File
@@ -4,25 +4,25 @@ using PalladiumWallet.Core.Chain;
namespace PalladiumWallet.Core.Crypto;
/// <summary>
/// Costruzione dei derivation path BIP44/49/84 (blueprint §4.2) e mappatura
/// ScriptKind → purpose / ScriptPubKeyType. I purpose sono costanti di protocollo
/// BIP (chain-independent); il coin_type viene sempre dal ChainProfile.
/// BIP44/49/84 derivation path construction (blueprint §4.2) and mapping of
/// ScriptKind → purpose / ScriptPubKeyType. Purposes are BIP protocol constants
/// (chain-independent); coin_type always comes from the ChainProfile.
/// </summary>
public static class DerivationPaths
{
/// <summary>Purpose BIP44 (P2PKH legacy).</summary>
/// <summary>BIP44 purpose (P2PKH legacy).</summary>
public const int PurposeLegacy = 44;
/// <summary>Purpose BIP49 (P2SH-P2WPKH).</summary>
/// <summary>BIP49 purpose (P2SH-P2WPKH).</summary>
public const int PurposeWrappedSegwit = 49;
/// <summary>Purpose BIP84 (P2WPKH nativo).</summary>
/// <summary>BIP84 purpose (native P2WPKH).</summary>
public const int PurposeNativeSegwit = 84;
/// <summary>Purpose BIP48 (multisig — fase successiva, §16 passo 8).</summary>
/// <summary>BIP48 purpose (multisig — next phase, §16 step 8).</summary>
public const int PurposeMultisig = 48;
/// <summary>Purpose BIP86 (P2TR key-path, Taproot).</summary>
/// <summary>BIP86 purpose (P2TR key-path, Taproot).</summary>
public const int PurposeTaproot = 86;
public static int PurposeFor(ScriptKind kind) => kind switch
@@ -36,9 +36,9 @@ public static class DerivationPaths
};
/// <summary>
/// Tipo di scriptPubKey NBitcoin per la derivazione da pubkey singola.
/// I tipi multisig derivano da redeem script, non da una pubkey: arriveranno
/// con i wallet M-di-N (§4.5).
/// NBitcoin scriptPubKey type for single-pubkey derivation.
/// Multisig types are derived from a redeem script, not a pubkey — they will
/// arrive with M-of-N wallet support (§4.5).
/// </summary>
public static ScriptPubKeyType ScriptPubKeyTypeFor(ScriptKind kind) => kind switch
{
@@ -48,13 +48,13 @@ public static class DerivationPaths
ScriptKind.Taproot => ScriptPubKeyType.TaprootBIP86,
ScriptKind.WrappedSegwitMultisig or ScriptKind.NativeSegwitMultisig =>
throw new NotSupportedException(
"I tipi multisig derivano da redeem script: supporto previsto con i wallet M-di-N (§4.5)."),
"Multisig types are derived from a redeem script: support planned with M-of-N wallets (§4.5)."),
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
};
/// <summary>
/// Path di account relativo alla root: purpose'/coin'/account' (§4.2).
/// Il coin_type è quello del profilo (746 mainnet, 1 testnet).
/// Account path relative to the root: purpose'/coin'/account' (§4.2).
/// coin_type is taken from the profile (746 mainnet, 1 testnet).
/// </summary>
public static KeyPath AccountPath(ScriptKind kind, ChainProfile profile, int account = 0)
{
@@ -64,7 +64,7 @@ public static class DerivationPaths
$"{PurposeFor(kind)}'/{profile.Bip44CoinType}'/{account}'");
}
/// <summary>Sottopath non-hardened change/index sotto l'account (change=0 receiving, change=1 change).</summary>
/// <summary>Non-hardened change/index sub-path under the account (change=0 receiving, change=1 change).</summary>
public static KeyPath AddressSubPath(bool isChange, int index)
{
if (index < 0)
@@ -73,8 +73,8 @@ public static class DerivationPaths
}
/// <summary>
/// Parsing di derivation path personalizzati in import (Sparrow-like, §4.2):
/// accetta il prefisso "m/" opzionale e gli hardened marker ' oppure h.
/// Parsing of custom derivation paths on import (Sparrow-like, §4.2):
/// accepts the optional "m/" prefix and hardened markers ' or h.
/// </summary>
public static bool TryParse(string path, out KeyPath? keyPath)
{
+28 -28
View File
@@ -4,12 +4,12 @@ using PalladiumWallet.Core.Chain;
namespace PalladiumWallet.Core.Crypto;
/// <summary>
/// Account HD single-sig (blueprint §4.2/§4.4): chiave estesa di account (xprv,
/// oppure solo xpub = watch-only) + tipo di script + profilo di rete. Deriva
/// indirizzi receiving (change=0) e change (change=1) on-demand per indice.
/// Gli indirizzi si derivano sempre dall'xpub di account (sottopath non-hardened),
/// quindi il watch-only funziona per costruzione. Il keystore completo
/// (cifratura, factory dal file wallet, §4.5) arriva con la persistenza (§8).
/// Single-sig HD account (blueprint §4.2/§4.4): extended account key (xprv,
/// or xpub-only = watch-only) + script type + network profile. Derives receiving
/// (change=0) and change (change=1) addresses on-demand by index.
/// Addresses are always derived from the account xpub (non-hardened sub-path),
/// so watch-only works by construction. The full keystore
/// (encryption, factory from wallet file, §4.5) arrives with persistence (§8).
/// </summary>
public sealed class HdAccount : IWalletAccount
{
@@ -18,25 +18,25 @@ public sealed class HdAccount : IWalletAccount
public ScriptKind Kind { get; }
public ChainProfile Profile { get; }
/// <summary>Path dell'account relativo alla root (es. 84'/746'/0', o personalizzato).</summary>
/// <summary>Account path relative to the root (e.g. 84'/746'/0', or custom).</summary>
public KeyPath AccountPath { get; }
/// <summary>
/// Fingerprint della chiave master: con <see cref="AccountPath"/> forma le
/// origin info richieste dalle PSBT (§6.5). Zero se ignota (xpub importata
/// senza metadati).
/// Master key fingerprint: together with <see cref="AccountPath"/> forms the
/// origin info required by PSBTs (§6.5). Zero if unknown (imported xpub
/// without metadata).
/// </summary>
public HDFingerprint MasterFingerprint { get; }
public ExtPubKey AccountXpub { get; }
/// <summary>True se l'account conosce solo la xpub: costruisce ma non firma (§4.5).</summary>
/// <summary>True if the account only knows the xpub: can build but not sign (§4.5).</summary>
public bool IsWatchOnly => _accountXprv is null;
private HdAccount(ExtKey? accountXprv, ExtPubKey accountXpub, ScriptKind kind,
ChainProfile profile, KeyPath accountPath, HDFingerprint masterFingerprint)
{
// Valida subito la mappatura (i tipi multisig non sono supportati qui).
// Validate the mapping immediately (multisig types are not supported here).
DerivationPaths.ScriptPubKeyTypeFor(kind);
_accountXprv = accountXprv;
AccountXpub = accountXpub;
@@ -46,7 +46,7 @@ public sealed class HdAccount : IWalletAccount
MasterFingerprint = masterFingerprint;
}
/// <summary>Caso principale: mnemonica BIP39 (+ passphrase opzionale) → account standard.</summary>
/// <summary>Main case: BIP39 mnemonic (+ optional passphrase) → standard account.</summary>
public static HdAccount FromMnemonic(Mnemonic mnemonic, string? passphrase,
ScriptKind kind, ChainProfile profile, int account = 0) =>
FromSeed(Bip39.ToSeed(mnemonic, passphrase), kind, profile, account);
@@ -55,22 +55,22 @@ public sealed class HdAccount : IWalletAccount
FromSeed(seed, kind, profile, DerivationPaths.AccountPath(kind, profile, account));
/// <summary>
/// Import con derivation path personalizzato (§4.2, Sparrow-like):
/// <paramref name="kind"/> determina solo il tipo di indirizzo generato.
/// Import with a custom derivation path (§4.2, Sparrow-like):
/// <paramref name="kind"/> only determines the generated address type.
/// </summary>
public static HdAccount FromSeed(byte[] seed, ScriptKind kind, ChainProfile profile, KeyPath accountPath)
{
var root = ExtKey.CreateFromSeed(seed);
// L'account si deriva sempre dalla xprv: i livelli hardened del path
// non sono derivabili da una xpub.
// The account is always derived from the xprv: hardened path levels
// cannot be derived from an xpub alone.
var accountXprv = root.Derive(accountPath);
return new HdAccount(accountXprv, accountXprv.Neuter(), kind, profile,
accountPath, root.Neuter().PubKey.GetHDFingerPrint());
}
/// <summary>
/// Watch-only da xpub di account (§4.4): fingerprint e path sono noti solo
/// se forniti da chi importa (servono per le origin info PSBT).
/// Watch-only from account xpub (§4.4): fingerprint and path are known only
/// if supplied by the importer (required for PSBT origin info).
/// </summary>
public static HdAccount FromAccountXpub(ExtPubKey accountXpub, ScriptKind kind,
ChainProfile profile, KeyPath? accountPath = null, HDFingerprint? masterFingerprint = null) =>
@@ -78,7 +78,7 @@ public sealed class HdAccount : IWalletAccount
accountPath ?? DerivationPaths.AccountPath(kind, profile),
masterFingerprint ?? default);
/// <summary>Import spendibile da xprv di account.</summary>
/// <summary>Spendable import from account xprv.</summary>
public static HdAccount FromAccountXprv(ExtKey accountXprv, ScriptKind kind,
ChainProfile profile, KeyPath? accountPath = null, HDFingerprint? masterFingerprint = null) =>
new(accountXprv, accountXprv.Neuter(), kind, profile,
@@ -97,27 +97,27 @@ public sealed class HdAccount : IWalletAccount
public PubKey? GetPublicKey(bool isChange, int index) =>
AccountXpub.Derive(DerivationPaths.AddressSubPath(isChange, index)).PubKey;
/// <summary>Chiave privata di un indirizzo; null se watch-only (§17).</summary>
/// <summary>Private key for an address; null if watch-only (§17).</summary>
public Key? GetPrivateKey(bool isChange, int index) =>
IsWatchOnly ? null : GetExtPrivateKey(isChange, index).PrivateKey;
/// <summary>Gli account HD usano il gap limit: nessuna lista fissa di indirizzi.</summary>
/// <summary>HD accounts use the gap limit: no fixed address list.</summary>
public IReadOnlyList<(BitcoinAddress Address, bool IsChange, int Index)>? FixedAddresses => null;
/// <summary>
/// Chiave privata estesa di un indirizzo. Lancia se watch-only: nessuna
/// chiave privata è derivabile dalle sole pubbliche (§17).
/// Extended private key for an address. Throws if watch-only: no private key
/// can be derived from public keys alone (§17).
/// </summary>
public ExtKey GetExtPrivateKey(bool isChange, int index) =>
(_accountXprv ?? throw new InvalidOperationException("Account watch-only: non può firmare."))
(_accountXprv ?? throw new InvalidOperationException("Watch-only account: cannot sign."))
.Derive(DerivationPaths.AddressSubPath(isChange, index));
/// <summary>Xpub di account in formato SLIP-132 (xpub/ypub/zpub secondo Kind).</summary>
/// <summary>Account xpub in SLIP-132 format (xpub/ypub/zpub according to Kind).</summary>
public string ToSlip132() => Slip132.Encode(AccountXpub, Kind, Profile);
/// <summary>Xprv di account in formato SLIP-132. Lancia se watch-only.</summary>
/// <summary>Account xprv in SLIP-132 format. Throws if watch-only.</summary>
public string ToSlip132Private() =>
Slip132.Encode(
_accountXprv ?? throw new InvalidOperationException("Account watch-only: nessuna chiave privata."),
_accountXprv ?? throw new InvalidOperationException("Watch-only account: no private key."),
Kind, Profile);
}
+8 -8
View File
@@ -4,31 +4,31 @@ using PalladiumWallet.Core.Chain;
namespace PalladiumWallet.Core.Crypto;
/// <summary>
/// Astrazione su tutti i tipi di account wallet (HD da seed, HD da xpub/xprv importata,
/// chiavi WIF importate). Consente a WalletSynchronizer e TransactionFactory di operare
/// indipendentemente dal tipo di keystore sottostante (blueprint §4.4–§4.5).
/// Abstraction over all wallet account types (HD from seed, HD from imported xpub/xprv,
/// imported WIF keys). Allows WalletSynchronizer and TransactionFactory to operate
/// independently of the underlying keystore type (blueprint §4.4–§4.5).
/// </summary>
public interface IWalletAccount
{
ScriptKind Kind { get; }
ChainProfile Profile { get; }
/// <summary>True se l'account non può firmare (assenza di chiavi private).</summary>
/// <summary>True if the account cannot sign (no private keys present).</summary>
bool IsWatchOnly { get; }
BitcoinAddress GetAddress(bool isChange, int index);
BitcoinAddress GetReceiveAddress(int index);
BitcoinAddress GetChangeAddress(int index);
/// <summary>Null se la chiave pubblica non è ricavabile dall'account (indirizzi puri watch-only).</summary>
/// <summary>Null if the public key cannot be derived from the account (pure watch-only addresses).</summary>
PubKey? GetPublicKey(bool isChange, int index);
/// <summary>Null se l'account è watch-only o l'indice è fuori range.</summary>
/// <summary>Null if the account is watch-only or the index is out of range.</summary>
Key? GetPrivateKey(bool isChange, int index);
/// <summary>
/// Per gli account con indirizzi fissi (WIF importati) restituisce la lista
/// completa da scansionare; null per gli account HD che usano il gap limit.
/// For accounts with fixed addresses (imported WIF keys) returns the full list
/// to scan; null for HD accounts that use the gap limit.
/// </summary>
IReadOnlyList<(BitcoinAddress Address, bool IsChange, int Index)>? FixedAddresses { get; }
}
+5 -5
View File
@@ -4,9 +4,9 @@ using PalladiumWallet.Core.Chain;
namespace PalladiumWallet.Core.Crypto;
/// <summary>
/// Account da chiavi WIF singole importate (blueprint §4.4 — "Imported"):
/// lista fissa di indirizzi, nessuna derivazione HD, nessuna catena di change.
/// Il change va sempre al primo indirizzo importato.
/// Account built from individually imported WIF keys (blueprint §4.4 — "Imported"):
/// fixed list of addresses, no HD derivation, no change chain.
/// Change always goes back to the first imported address.
/// </summary>
public sealed class ImportedKeyAccount : IWalletAccount
{
@@ -24,7 +24,7 @@ public sealed class ImportedKeyAccount : IWalletAccount
ScriptKind kind, ChainProfile profile)
{
if (entries.Count == 0)
throw new ArgumentException("Almeno un indirizzo richiesto.", nameof(entries));
throw new ArgumentException("At least one address is required.", nameof(entries));
_entries = [.. entries];
Kind = kind;
Profile = profile;
@@ -39,7 +39,7 @@ public sealed class ImportedKeyAccount : IWalletAccount
public BitcoinAddress GetReceiveAddress(int index) => GetAddress(false, index);
/// <summary>Il change torna sempre al primo indirizzo importato.</summary>
/// <summary>Change always returns to the first imported address.</summary>
public BitcoinAddress GetChangeAddress(int index) => _entries[0].Address;
public PubKey? GetPublicKey(bool isChange, int index)
+2 -2
View File
@@ -20,8 +20,8 @@ public static class Slip132
Encoders.Base58Check.EncodeData([.. profile.ExtKeyHeaders[kind].PrivateBytes(), .. key.ToBytes()]);
/// <summary>
/// Riconosce l'header (→ ScriptKind) e decodifica una chiave pubblica estesa:
/// è la via dell'import watch-only (§4.4).
/// Recognises the header (→ ScriptKind) and decodes an extended public key:
/// this is the watch-only import path (§4.4).
/// </summary>
public static bool TryDecodePublic(string encoded, ChainProfile profile,
out ExtPubKey? key, out ScriptKind kind)