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:
@@ -21,7 +21,7 @@ public class ChainProfileTests
|
||||
Assert.Equal(50002, p.DefaultSslPort);
|
||||
Assert.Equal(746, p.Bip44CoinType);
|
||||
Assert.Equal("palladium", p.UriScheme);
|
||||
Assert.True(p.SkipPowValidation); // obbligatorio: la catena usa LWMA (§3)
|
||||
Assert.True(p.SkipPowValidation); // mandatory: the chain uses LWMA (§3)
|
||||
Assert.Equal(120, p.BlockTimeSeconds);
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ public class ChainProfileTests
|
||||
Assert.Equal(1, ChainProfiles.Testnet.Bip44CoinType);
|
||||
}
|
||||
|
||||
// Serializza header (4 byte BE) + payload BIP32 di 74 byte e codifica Base58Check:
|
||||
// il prefisso testuale risultante dipende solo dall'header.
|
||||
// Serialize header (4-byte BE) + 74-byte BIP32 payload and encode Base58Check:
|
||||
// the resulting textual prefix depends only on the header.
|
||||
private static string EncodeWithHeader(uint header)
|
||||
{
|
||||
var data = new byte[78];
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace PalladiumWallet.Tests.Chain;
|
||||
|
||||
public class PalladiumNetworksTests
|
||||
{
|
||||
// Chiave privata fissa per test deterministici (solo test, mai usarla davvero).
|
||||
// Fixed private key for deterministic tests (test-only, never use it for real).
|
||||
private static Key TestKey => new(Convert.FromHexString(
|
||||
"0000000000000000000000000000000000000000000000000000000000000001"));
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public class AddressDerivationTests
|
||||
[Fact]
|
||||
public void Il_vettore_bip44_produce_lo_stesso_hash_su_bitcoin_e_plm()
|
||||
{
|
||||
// Indirizzo noto di abandon-about su m/44'/0'/0'/0/0 (riferimento pubblico).
|
||||
// Known abandon-about address at m/44'/0'/0'/0/0 (public reference).
|
||||
var account = HdAccount.FromSeed(AbandonAboutSeed(), ScriptKind.Legacy,
|
||||
ChainProfiles.Mainnet, new KeyPath("44'/0'/0'"));
|
||||
var pubKey = account.GetPublicKey(isChange: false, 0);
|
||||
@@ -27,8 +27,8 @@ public class AddressDerivationTests
|
||||
|
||||
var plmAddr = account.GetReceiveAddress(0);
|
||||
Assert.StartsWith("P", plmAddr.ToString());
|
||||
// Stesso hash160 sotto i due prefissi: la derivazione è identica,
|
||||
// cambia solo la veste di rete.
|
||||
// Same hash160 under the two prefixes: the derivation is identical,
|
||||
// only the network dress changes.
|
||||
Assert.Equal(pubKey.Hash, ((BitcoinPubKeyAddress)plmAddr).Hash);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ using PalladiumWallet.Core.Crypto;
|
||||
namespace PalladiumWallet.Tests.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Vettore di test 1 della specifica BIP32 (seed 000102...0e0f). Gli header
|
||||
/// Legacy della mainnet PLM coincidono con quelli Bitcoin, quindi il confronto
|
||||
/// con le stringhe della specifica è diretto sulla rete PLM.
|
||||
/// BIP32 specification test vector 1 (seed 000102...0e0f). The Legacy headers
|
||||
/// of the PLM mainnet coincide with the Bitcoin ones, so the comparison
|
||||
/// with the specification strings is direct on the PLM network.
|
||||
/// </summary>
|
||||
public class Bip32Tests
|
||||
{
|
||||
@@ -40,7 +40,7 @@ public class Bip32Tests
|
||||
[Fact]
|
||||
public void I_path_hardened_non_sono_derivabili_da_una_xpub()
|
||||
{
|
||||
// Garanzia §17: da sole chiavi pubbliche niente derivazione hardened.
|
||||
// §17 guarantee: from public keys alone, no hardened derivation.
|
||||
Assert.ThrowsAny<InvalidOperationException>(() =>
|
||||
Root.Neuter().Derive(new KeyPath("0'")));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace PalladiumWallet.Tests.Crypto;
|
||||
|
||||
public class Bip39Tests
|
||||
{
|
||||
// Vettori ufficiali Trezor (python-mnemonic/vectors.json), passphrase "TREZOR".
|
||||
// Official Trezor vectors (python-mnemonic/vectors.json), passphrase "TREZOR".
|
||||
[Theory]
|
||||
[InlineData(
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||
@@ -30,8 +30,8 @@ public class Bip39Tests
|
||||
[Fact]
|
||||
public void Il_vettore_giapponese_copre_la_normalizzazione_nfkd()
|
||||
{
|
||||
// Vettore ufficiale bip32JP (test_JP_BIP39.json[0]): mnemonica con spazi
|
||||
// ideografici U+3000 e passphrase con caratteri da normalizzare NFKD.
|
||||
// Official bip32JP vector (test_JP_BIP39.json[0]): mnemonic with U+3000
|
||||
// ideographic spaces and a passphrase with characters to normalize NFKD.
|
||||
const string words =
|
||||
"あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら";
|
||||
const string passphrase = "㍍ガバヴァぱばぐゞちぢ十人十色";
|
||||
@@ -55,8 +55,8 @@ public class Bip39Tests
|
||||
[Fact]
|
||||
public void Checksum_invalido_viene_rifiutato()
|
||||
{
|
||||
// 12 × "abandon": parole valide ma checksum errato — il caso che il
|
||||
// costruttore NBitcoin non controlla da solo.
|
||||
// 12 × "abandon": valid words but wrong checksum — the case that the
|
||||
// NBitcoin constructor does not check on its own.
|
||||
var words = string.Join(' ', Enumerable.Repeat("abandon", 12));
|
||||
Assert.False(Bip39.TryParse(words, out _));
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ using PalladiumWallet.Core.Crypto;
|
||||
namespace PalladiumWallet.Tests.Crypto;
|
||||
|
||||
/// <summary>
|
||||
/// Vettore di test della specifica BIP84 (mnemonica abandon-about, senza
|
||||
/// passphrase). Gli header SLIP-132 della mainnet PLM coincidono con quelli
|
||||
/// Bitcoin, quindi zprv/zpub si confrontano direttamente; gli indirizzi si
|
||||
/// confrontano sul witness program (chain-independent) + prefisso PLM.
|
||||
/// Il path m/84'/0'/0' è volutamente "personalizzato" (coin type 0, non 746):
|
||||
/// esercita anche l'import con path custom (§4.2).
|
||||
/// BIP84 specification test vector (abandon-about mnemonic, without
|
||||
/// passphrase). The SLIP-132 headers of the PLM mainnet coincide with the
|
||||
/// Bitcoin ones, so zprv/zpub are compared directly; addresses are
|
||||
/// compared on the witness program (chain-independent) + PLM prefix.
|
||||
/// The path m/84'/0'/0' is deliberately "customized" (coin type 0, not 746):
|
||||
/// it also exercises the import with a custom path (§4.2).
|
||||
/// </summary>
|
||||
public class Bip84Slip132Tests
|
||||
{
|
||||
@@ -64,12 +64,12 @@ public class Bip84Slip132Tests
|
||||
[Fact]
|
||||
public void Una_xkey_con_header_sconosciuto_viene_rifiutata()
|
||||
{
|
||||
// xpub Bitcoin valida ma con header Legacy: non è una zpub.
|
||||
// Valid Bitcoin xpub but with Legacy header: it is not a zpub.
|
||||
var account = Account();
|
||||
var asXpub = account.AccountXpub.ToString(Network.Main);
|
||||
|
||||
Assert.True(Slip132.TryDecodePublic(asXpub, ChainProfiles.Mainnet, out _, out var kind));
|
||||
Assert.Equal(ScriptKind.Legacy, kind); // header xpub → riconosciuto come Legacy
|
||||
Assert.Equal(ScriptKind.Legacy, kind); // xpub header → recognized as Legacy
|
||||
Assert.False(Slip132.TryDecodePublic("non-base58!!!", ChainProfiles.Mainnet, out _, out _));
|
||||
Assert.False(Slip132.TryDecodePrivate(asXpub, ChainProfiles.Mainnet, out _, out _)); // pub ≠ priv
|
||||
}
|
||||
@@ -88,8 +88,8 @@ public class Bip84Slip132Tests
|
||||
if (expectedPubKeyHex is not null)
|
||||
Assert.Equal(expectedPubKeyHex, pubKey.ToHex());
|
||||
|
||||
// Il witness program (hash160 della pubkey) è chain-independent: deve
|
||||
// coincidere con quello dell'indirizzo bc1 del vettore ufficiale.
|
||||
// The witness program (hash160 of the pubkey) is chain-independent: it must
|
||||
// coincide with that of the bc1 address from the official vector.
|
||||
var bitcoinExpected = (BitcoinWitPubKeyAddress)BitcoinAddress.Create(bitcoinAddress, Network.Main);
|
||||
Assert.Equal(bitcoinExpected.Hash, pubKey.WitHash);
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ 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.
|
||||
/// BIP86 test vectors (abandon-about mnemonic, no passphrase).
|
||||
/// The tweaked public key (output key, 32-byte x-only) is chain-independent:
|
||||
/// verified against the official Bitcoin vectors, then checked that the PLM
|
||||
/// address starts with plm1p (witness v1, bech32m).
|
||||
/// The path m/86'/0'/0' uses coin_type=0 (not 746) to match the BIP86 vectors.
|
||||
/// </summary>
|
||||
public class Bip86TaprootTests
|
||||
{
|
||||
@@ -46,9 +46,9 @@ public class Bip86TaprootTests
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// The output key (tweaked x-only pubkey, 32 bytes) matches the BIP86 vector
|
||||
/// regardless of network: the network changes only HRP and checksum, not the program.
|
||||
/// Bitcoin addresses from https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(false, 0, "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr")]
|
||||
@@ -60,7 +60,7 @@ public class Bip86TaprootTests
|
||||
var account = Account();
|
||||
var plmAddr = account.GetAddress(isChange, index);
|
||||
|
||||
// Il witness program (output key tweakizzata) è chain-independent
|
||||
// The witness program (tweaked output key) is chain-independent.
|
||||
var btcAddr = (TaprootAddress)BitcoinAddress.Create(bitcoinAddress, Network.Main);
|
||||
var plmTaproot = (TaprootAddress)plmAddr;
|
||||
Assert.Equal(btcAddr.PubKey, plmTaproot.PubKey);
|
||||
|
||||
@@ -9,7 +9,7 @@ public class PeerParsingTests
|
||||
[Fact]
|
||||
public void La_risposta_peers_subscribe_si_parsa_nel_formato_electrumx()
|
||||
{
|
||||
// Formato reale: [ip, hostname, ["v...", "pN", "tPORTA", "sPORTA"]].
|
||||
// Real format: [ip, hostname, ["v...", "pN", "tPORT", "sPORT"]].
|
||||
const string json = """
|
||||
[
|
||||
["173.212.224.67", "173.212.224.67", ["v1.4.2", "p10000", "t50001", "s50002"]],
|
||||
@@ -20,10 +20,10 @@ public class PeerParsingTests
|
||||
""";
|
||||
var peers = ElectrumApi.ParsePeers(JsonDocument.Parse(json).RootElement);
|
||||
|
||||
Assert.Equal(3, peers.Count); // l'ultimo non offre porte → scartato
|
||||
Assert.Equal(3, peers.Count); // last entry has no ports → discarded
|
||||
|
||||
Assert.Equal(new PeerInfo("173.212.224.67", 50001, 50002, "1.4.2"), peers[0]);
|
||||
// "t" senza numero = porta di default (0 segnala "da risolvere col profilo").
|
||||
// "t" without a number = default port (0 signals "resolve from profile").
|
||||
Assert.Equal(new PeerInfo("nodo.esempio.org", 0, null, "1.4"), peers[1]);
|
||||
Assert.Equal(new PeerInfo("solo-ssl.esempio.org", null, 50002, "1.4"), peers[2]);
|
||||
}
|
||||
@@ -55,8 +55,8 @@ public class ServerRegistryTests
|
||||
var registry = new ServerRegistry(ChainProfiles.Mainnet, path);
|
||||
var bootstrapCount = registry.All.Count;
|
||||
|
||||
// Merge diretto via DiscoverAsync richiede un client: si testa la
|
||||
// persistenza simulando il file degli scoperti.
|
||||
// Direct merge via DiscoverAsync requires a client: test persistence
|
||||
// by simulating the discovered servers file.
|
||||
var discovered = new[] { new KnownServer("nuovo.esempio.org", 50001, 50002, "1.4.2") };
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(discovered));
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ServerRegistryTests
|
||||
Assert.Equal(bootstrapCount + 1, reloaded.All.Count);
|
||||
Assert.Contains(reloaded.All, s => s.Host == "nuovo.esempio.org");
|
||||
|
||||
// Un bootstrap duplicato nel file non raddoppia.
|
||||
// A bootstrap duplicate in the file must not double-count.
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(new[]
|
||||
{
|
||||
new KnownServer("173.212.224.67", 50001, 50002),
|
||||
|
||||
@@ -9,18 +9,18 @@ using PalladiumWallet.Core.Wallet;
|
||||
namespace PalladiumWallet.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Property-based tests (CsCheck). Ogni test genera centinaia di input casuali e
|
||||
/// verifica che le proprietà invarianti reggano — crash, eccezioni non attese, o
|
||||
/// violazioni di roundtrip sono failures.
|
||||
/// Property-based tests (CsCheck). Each test generates hundreds of random inputs and
|
||||
/// verifies that invariant properties hold — crashes, unexpected exceptions, or
|
||||
/// roundtrip violations are failures.
|
||||
/// </summary>
|
||||
public class PropertyTests
|
||||
{
|
||||
// ── generatori riutilizzabili ─────────────────────────────────────────────
|
||||
// ── reusable generators ───────────────────────────────────────────────────
|
||||
|
||||
private static readonly Gen<string> GenUnit = Gen.OneOf(
|
||||
Gen.Const("PLM"), Gen.Const("mPLM"), Gen.Const("µPLM"), Gen.Const("sat"));
|
||||
|
||||
// uint256 casuale costruito da 4 ulong
|
||||
// random uint256 built from 4 ulongs
|
||||
private static readonly Gen<uint256> GenTxid =
|
||||
Gen.Select(Gen.ULong, Gen.ULong, Gen.ULong, Gen.ULong, (a, b, c, d) =>
|
||||
{
|
||||
@@ -32,9 +32,9 @@ public class PropertyTests
|
||||
return new uint256(bytes);
|
||||
});
|
||||
|
||||
// ── CoinAmount ──────────────────────────────────────────────────────────
|
||||
// ── CoinAmount ───────────────────────────────────────────────────────────
|
||||
|
||||
/// TryParseIn non deve mai lanciare eccezioni su input arbitrario con unità valide.
|
||||
/// TryParseIn must never throw on arbitrary input with valid units.
|
||||
[Fact]
|
||||
public void CoinAmount_TryParseIn_non_lancia_mai_su_input_arbitrario()
|
||||
{
|
||||
@@ -46,17 +46,17 @@ public class PropertyTests
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// unità sconosciuta: impossibile qui perché usiamo solo unità note
|
||||
// unknown unit: impossible here because we only use known units
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"TryParseIn ha lanciato {ex.GetType().Name} per unit={unit}");
|
||||
Assert.Fail($"TryParseIn threw {ex.GetType().Name} for unit={unit}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// FormatIn → TryParseIn: qualsiasi satoshi [0, MaxSupply] deve fare roundtrip esatto.
|
||||
/// FormatIn → TryParseIn: any satoshi value in [0, MaxSupply] must roundtrip exactly.
|
||||
[Fact]
|
||||
public void CoinAmount_roundtrip_FormatIn_TryParseIn_per_ogni_unita()
|
||||
{
|
||||
@@ -66,12 +66,12 @@ public class PropertyTests
|
||||
var formatted = CoinAmount.FormatIn(sats, unit, withLabel: false);
|
||||
Assert.True(
|
||||
CoinAmount.TryParseIn(formatted, unit, out var parsed),
|
||||
$"FormatIn={formatted} unit={unit} non si riparsa");
|
||||
$"FormatIn={formatted} unit={unit} failed to re-parse");
|
||||
Assert.Equal(sats, parsed);
|
||||
});
|
||||
}
|
||||
|
||||
/// TryParseCoins non deve mai lanciare su input arbitrario.
|
||||
/// TryParseCoins must never throw on arbitrary input.
|
||||
[Fact]
|
||||
public void CoinAmount_TryParseCoins_non_lancia_mai_su_input_arbitrario()
|
||||
{
|
||||
@@ -83,25 +83,25 @@ public class PropertyTests
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"TryParseCoins ha lanciato {ex.GetType().Name}");
|
||||
Assert.Fail($"TryParseCoins threw {ex.GetType().Name}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Qualsiasi valore accettato da TryParseCoins deve essere ≥ 0.
|
||||
/// Any value accepted by TryParseCoins must be ≥ 0.
|
||||
[Fact]
|
||||
public void CoinAmount_TryParseCoins_accetta_solo_valori_non_negativi()
|
||||
{
|
||||
Gen.String.Sample(text =>
|
||||
{
|
||||
if (CoinAmount.TryParseCoins(text, out var sats))
|
||||
Assert.True(sats >= 0, $"TryParseCoins ha restituito {sats} per '{text}'");
|
||||
Assert.True(sats >= 0, $"TryParseCoins returned {sats} for '{text}'");
|
||||
});
|
||||
}
|
||||
|
||||
// ── EncryptedFile ────────────────────────────────────────────────────────
|
||||
// ── EncryptedFile ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Encrypt → Decrypt con la stessa password deve restituire il testo originale.
|
||||
/// Encrypt → Decrypt with the same password must return the original plaintext.
|
||||
[Fact]
|
||||
public void EncryptedFile_roundtrip_su_contenuto_e_password_arbitrari()
|
||||
{
|
||||
@@ -113,29 +113,29 @@ public class PropertyTests
|
||||
});
|
||||
}
|
||||
|
||||
/// Decrypt con password sbagliata deve lanciare WrongPasswordException, mai altro.
|
||||
/// Decrypt with the wrong password must throw WrongPasswordException, never anything else.
|
||||
[Fact]
|
||||
public void EncryptedFile_password_sbagliata_lancia_solo_WrongPasswordException()
|
||||
{
|
||||
Gen.Select(Gen.String, Gen.String[1, 32], Gen.String[1, 32]).Sample((plaintext, pwd1, pwd2) =>
|
||||
{
|
||||
if (pwd1 == pwd2) return; // stessa password: roundtrip valido, salta
|
||||
if (pwd1 == pwd2) return; // same password: valid roundtrip, skip
|
||||
|
||||
var cipher = EncryptedFile.Encrypt(plaintext, pwd1);
|
||||
try
|
||||
{
|
||||
EncryptedFile.Decrypt(cipher, pwd2);
|
||||
Assert.Fail("Decrypt con password sbagliata non ha lanciato");
|
||||
Assert.Fail("Decrypt with wrong password did not throw");
|
||||
}
|
||||
catch (WrongPasswordException) { /* atteso */ }
|
||||
catch (WrongPasswordException) { /* expected */ }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"Decrypt ha lanciato {ex.GetType().Name} invece di WrongPasswordException");
|
||||
Assert.Fail($"Decrypt threw {ex.GetType().Name} instead of WrongPasswordException");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// IsEncrypted non deve mai lanciare su input arbitrario.
|
||||
/// IsEncrypted must never throw on arbitrary input.
|
||||
[Fact]
|
||||
public void EncryptedFile_IsEncrypted_non_lancia_mai()
|
||||
{
|
||||
@@ -144,14 +144,14 @@ public class PropertyTests
|
||||
try { EncryptedFile.IsEncrypted(s); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Assert.Fail($"IsEncrypted ha lanciato {ex.GetType().Name}");
|
||||
Assert.Fail($"IsEncrypted threw {ex.GetType().Name}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── MerkleProof ──────────────────────────────────────────────────────────
|
||||
// ── MerkleProof ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Ogni foglia di un albero Merkle generato casualmente deve verificare contro la radice.
|
||||
/// Every leaf of a randomly generated Merkle tree must verify against the root.
|
||||
[Fact]
|
||||
public void MerkleProof_ogni_foglia_verifica_contro_la_sua_radice()
|
||||
{
|
||||
@@ -163,18 +163,18 @@ public class PropertyTests
|
||||
var branch = BuildBranch(txids, pos);
|
||||
Assert.True(
|
||||
MerkleProof.Verify(txids[pos], pos, branch, root),
|
||||
$"Verify fallita per pos={pos} su {txids.Length} foglie");
|
||||
$"Verify failed for pos={pos} over {txids.Length} leaves");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Un txid non presente nelle foglie non deve verificare (e non deve crashare).
|
||||
/// A txid not present in the leaves must not verify (and must not crash).
|
||||
[Fact]
|
||||
public void MerkleProof_txid_estraneo_non_verifica_e_non_crasha()
|
||||
{
|
||||
Gen.Select(GenTxid.Array[2, 8], GenTxid).Sample((txids, extra) =>
|
||||
{
|
||||
if (txids.Contains(extra)) return; // collisione casuale: salta
|
||||
if (txids.Contains(extra)) return; // random collision: skip
|
||||
|
||||
var root = MerkleProof.ComputeRootFromLeaves(txids);
|
||||
var branch = BuildBranch(txids, 0);
|
||||
@@ -182,7 +182,7 @@ public class PropertyTests
|
||||
});
|
||||
}
|
||||
|
||||
// helper: costruisce il branch per la posizione data
|
||||
// helper: builds the branch for the given position
|
||||
private static List<uint256> BuildBranch(IReadOnlyList<uint256> leaves, int position)
|
||||
{
|
||||
var branch = new List<uint256>();
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace PalladiumWallet.Tests.Spv;
|
||||
|
||||
public class ScripthashTests
|
||||
{
|
||||
// Vettori calcolati indipendentemente con Python (hashlib), non con NBitcoin.
|
||||
// Vectors computed independently with Python (hashlib), not with NBitcoin.
|
||||
[Theory]
|
||||
[InlineData("76a9140102030405060708090a0b0c0d0e0f101112131488ac",
|
||||
"5546fc69d399ef99854c132abb060381cc159dbec67c496a6f0e0dbf12e83ae8")]
|
||||
@@ -39,7 +39,7 @@ public class ScripthashTests
|
||||
|
||||
public class MerkleProofTests
|
||||
{
|
||||
// Blocco Bitcoin 100000: 4 transazioni (pari), merkle root nota.
|
||||
// Bitcoin block 100000: 4 transactions (even), known merkle root.
|
||||
private static readonly uint256[] Block100000Txids =
|
||||
[
|
||||
uint256.Parse("8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87"),
|
||||
@@ -51,7 +51,7 @@ public class MerkleProofTests
|
||||
private static readonly uint256 Block100000Root =
|
||||
uint256.Parse("f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766");
|
||||
|
||||
// ---- numero pari di transazioni (4 tx) ----
|
||||
// ---- even number of transactions (4 tx) ----
|
||||
|
||||
[Fact]
|
||||
public void La_radice_calcolata_dalle_foglie_coincide_con_quella_del_blocco()
|
||||
@@ -88,13 +88,13 @@ public class MerkleProofTests
|
||||
Assert.True(MerkleProof.Verify(txid, 0, [], root));
|
||||
}
|
||||
|
||||
// ---- numero dispari di transazioni (duplicazione dell'ultimo) ----
|
||||
// ---- odd number of transactions (last one duplicated) ----
|
||||
|
||||
[Fact]
|
||||
public void Tre_tx_dispari_la_terza_viene_duplicata()
|
||||
{
|
||||
// Con 3 tx: livello 1 = [SHA256d(tx0||tx1), SHA256d(tx2||tx2)]
|
||||
// Verifica che la radice sia deterministica e corretta.
|
||||
// With 3 tx: level 1 = [SHA256d(tx0||tx1), SHA256d(tx2||tx2)]
|
||||
// Verify the root is deterministic and correct.
|
||||
var txids = Block100000Txids.Take(3).ToArray();
|
||||
var root = MerkleProof.ComputeRootFromLeaves(txids);
|
||||
var branch2 = BuildBranch(txids, 2);
|
||||
@@ -104,7 +104,7 @@ public class MerkleProofTests
|
||||
[Fact]
|
||||
public void Cinque_tx_dispari_verifica_tutte_le_posizioni()
|
||||
{
|
||||
// 5 tx → livello pari (4) → livello pari (2) → radice
|
||||
// 5 tx → even level (4) → even level (2) → root
|
||||
var txids = new uint256[5];
|
||||
for (var i = 0; i < 5; i++)
|
||||
txids[i] = new uint256(new byte[32].Select((_, j) => (byte)(i * 17 + j)).ToArray());
|
||||
@@ -118,7 +118,7 @@ public class MerkleProofTests
|
||||
}
|
||||
}
|
||||
|
||||
// ---- due transazioni (pari minimo) ----
|
||||
// ---- two transactions (minimum even) ----
|
||||
|
||||
[Fact]
|
||||
public void Due_tx_verifica_entrambe_le_posizioni()
|
||||
@@ -132,7 +132,7 @@ public class MerkleProofTests
|
||||
}
|
||||
}
|
||||
|
||||
// ---- proof errate ----
|
||||
// ---- invalid proofs ----
|
||||
|
||||
[Fact]
|
||||
public void Una_prova_per_la_posizione_sbagliata_fallisce()
|
||||
@@ -152,7 +152,7 @@ public class MerkleProofTests
|
||||
public void Branch_alterato_non_verifica()
|
||||
{
|
||||
var branch = BuildBranch(Block100000Txids, 0);
|
||||
branch[0] = uint256.One; // corrompe il primo elemento del branch
|
||||
branch[0] = uint256.One; // corrupt the first branch element
|
||||
Assert.False(MerkleProof.Verify(Block100000Txids[0], 0, branch, Block100000Root));
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public class MerkleProofTests
|
||||
Assert.Throws<ArgumentException>(() => MerkleProof.ComputeRootFromLeaves([]));
|
||||
}
|
||||
|
||||
/// <summary>Costruisce il branch per una foglia ricostruendo i livelli dell'albero.</summary>
|
||||
/// <summary>Builds the Merkle branch for a leaf by reconstructing the tree levels.</summary>
|
||||
private static List<uint256> BuildBranch(IReadOnlyList<uint256> leaves, int position)
|
||||
{
|
||||
var branch = new List<uint256>();
|
||||
|
||||
@@ -20,7 +20,7 @@ public class StorageTests
|
||||
private static string TempPath() =>
|
||||
Path.Combine(Path.GetTempPath(), $"plm-test-{Guid.NewGuid()}.wallet.json");
|
||||
|
||||
// ---- cifratura AES-GCM ----
|
||||
// ---- AES-GCM encryption ----
|
||||
|
||||
[Fact]
|
||||
public void La_cifratura_fa_roundtrip_con_la_password_giusta()
|
||||
@@ -257,8 +257,8 @@ public class StorageTests
|
||||
[Fact]
|
||||
public void WalletLock_file_lock_preesistente_ma_non_bloccato_viene_acquisito()
|
||||
{
|
||||
// Un .lock rimasto da un crash precedente (file esiste ma nessuno lo tiene)
|
||||
// non deve bloccare l'apertura del wallet.
|
||||
// A stale .lock left from a previous crash (file exists but nobody holds it)
|
||||
// must not block wallet opening.
|
||||
var path = TempPath();
|
||||
var lockPath = path + ".lock";
|
||||
try
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace PalladiumWallet.Tests.Wallet;
|
||||
|
||||
public class CoinAmountTests
|
||||
{
|
||||
// ---- importi validi: tutte le unità ----
|
||||
// ---- valid amounts: all units ----
|
||||
|
||||
[Theory]
|
||||
[InlineData("1", "sat", 1)]
|
||||
@@ -96,7 +96,7 @@ public class CoinAmountTests
|
||||
Assert.False(CoinAmount.TryParseIn("99999999999", "PLM", out _));
|
||||
}
|
||||
|
||||
// ---- unità sconosciuta lancia ArgumentException ----
|
||||
// ---- unknown unit throws ArgumentException ----
|
||||
|
||||
[Theory]
|
||||
[InlineData("banana")]
|
||||
|
||||
@@ -6,15 +6,15 @@ using PalladiumWallet.Core.Wallet;
|
||||
namespace PalladiumWallet.Tests.Wallet;
|
||||
|
||||
/// <summary>
|
||||
/// Test per ImportedKeyAccount e i nuovi percorsi factory in WalletLoader
|
||||
/// (blueprint §4.4 — importati WIF, xpub, xprv).
|
||||
/// Tests for ImportedKeyAccount and the new factory paths in WalletLoader
|
||||
/// (blueprint §4.4 — imported WIF, xpub, xprv).
|
||||
/// </summary>
|
||||
public class ImportedKeyAccountTests
|
||||
{
|
||||
private static ChainProfile Profile => ChainProfiles.Mainnet;
|
||||
private static Network Network => PalladiumNetworks.For(Profile.Kind);
|
||||
|
||||
// Chiave WIF valida per PLM mainnet (prefix 0x80 = Compressed WIF "K"/"L")
|
||||
// Valid WIF key for PLM mainnet (prefix 0x80 = Compressed WIF "K"/"L")
|
||||
private static Key GenerateKey() => new Key();
|
||||
|
||||
private static string ToWif(Key key) => key.GetWif(Network).ToString();
|
||||
@@ -43,7 +43,7 @@ public class ImportedKeyAccountTests
|
||||
|
||||
Assert.Equal(addr.ToString(), account.GetAddress(false, 0).ToString());
|
||||
Assert.Equal(addr.ToString(), account.GetReceiveAddress(0).ToString());
|
||||
// Change → primo indirizzo
|
||||
// Change → first address
|
||||
Assert.Equal(addr.ToString(), account.GetChangeAddress(0).ToString());
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public class ImportedKeyAccountTests
|
||||
[Fact]
|
||||
public void NewFromXpub_produce_account_watch_only()
|
||||
{
|
||||
// Crea un HD account, esporta la zpub, reimporta come xpub watch-only.
|
||||
// Create an HD account, export the zpub, re-import as watch-only xpub.
|
||||
var (_, hdFull) = WalletLoader.NewFromMnemonic(
|
||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||
null, ScriptKind.NativeSegwit, Profile);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class TransactionFactoryTests
|
||||
return HdAccount.FromMnemonic(mnemonic!, null, ScriptKind.NativeSegwit, Profile);
|
||||
}
|
||||
|
||||
/// <summary>Tx fittizia che accredita <paramref name="sats"/> sull'indirizzo receiving/0.</summary>
|
||||
/// <summary>Fake transaction that credits <paramref name="sats"/> to the receiving/0 address.</summary>
|
||||
private static (List<CachedUtxo>, Dictionary<string, Transaction>) Fund(HdAccount account, long sats)
|
||||
{
|
||||
var funding = Net.CreateTransaction();
|
||||
@@ -51,17 +51,17 @@ public class TransactionFactoryTests
|
||||
feeRateSatPerVByte: 2, changeIndex: 0);
|
||||
|
||||
Assert.True(built.Signed);
|
||||
// Output: destinatario + change.
|
||||
// Output: recipient + change.
|
||||
Assert.Equal(2, built.Transaction.Outputs.Count);
|
||||
Assert.Contains(built.Transaction.Outputs,
|
||||
o => o.ScriptPubKey == destination.ScriptPubKey && o.Value.Satoshi == 400_000);
|
||||
|
||||
// Fee coerente col rate richiesto (±20% per gli arrotondamenti di stima).
|
||||
// Fee consistent with the requested rate (±20% for estimation rounding).
|
||||
var vsize = built.Transaction.GetVirtualSize();
|
||||
var expected = vsize * 2;
|
||||
Assert.InRange(built.Fee.Satoshi, expected * 0.8, expected * 1.5);
|
||||
|
||||
// RBF abilitato (§6.6).
|
||||
// RBF enabled (§6.6).
|
||||
Assert.All(built.Transaction.Inputs, i => Assert.True(i.Sequence < Sequence.Final));
|
||||
}
|
||||
|
||||
@@ -96,12 +96,12 @@ public class TransactionFactoryTests
|
||||
{
|
||||
var account = Account();
|
||||
var (utxos, txs) = Fund(account, 1_000_000);
|
||||
utxos[0].Height = 0; // in mempool: visibile nel saldo pending, non spendibile
|
||||
utxos[0].Height = 0; // in mempool: visible in pending balance, not spendable
|
||||
|
||||
var ex = Assert.Throws<WalletSpendException>(() => new TransactionFactory(account).Build(
|
||||
utxos, txs, account.GetReceiveAddress(1), amountSats: 100_000,
|
||||
feeRateSatPerVByte: 2, changeIndex: 0));
|
||||
Assert.Contains("in attesa di conferma", ex.Message);
|
||||
Assert.Contains("pending confirmation", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -109,7 +109,7 @@ public class TransactionFactoryTests
|
||||
{
|
||||
var account = Account();
|
||||
var (utxos, txs) = Fund(account, 1_000_000);
|
||||
utxos[0].Frozen = true; // freeze (§6.2)
|
||||
utxos[0].Frozen = true; // frozen (§6.2)
|
||||
|
||||
Assert.Throws<WalletSpendException>(() => new TransactionFactory(account).Build(
|
||||
utxos, txs, account.GetReceiveAddress(1), amountSats: 100_000,
|
||||
@@ -130,8 +130,8 @@ public class TransactionFactoryTests
|
||||
feeRateSatPerVByte: 2, changeIndex: 0);
|
||||
|
||||
Assert.False(built.Signed);
|
||||
// Il flusso air-gapped (§6.5): la PSBT della macchina online si firma
|
||||
// offline con le chiavi e si finalizza.
|
||||
// Air-gapped flow (§6.5): the online-machine PSBT is signed
|
||||
// offline with the keys and then finalised.
|
||||
var psbt = built.Psbt;
|
||||
psbt.SignWithKeys(full.GetExtPrivateKey(false, 0));
|
||||
psbt.Finalize();
|
||||
@@ -157,7 +157,7 @@ public class TransactionFactoryTests
|
||||
Assert.False(CoinAmount.TryParseCoins("-1", out _));
|
||||
}
|
||||
|
||||
// 1.5 PLM = 150_000_000 sat, espressi in ciascuna unità (§8).
|
||||
// 1.5 PLM = 150_000_000 sat, expressed in each unit (§8).
|
||||
[Theory]
|
||||
[InlineData("PLM", "1.50000000 PLM", "1.5")]
|
||||
[InlineData("mPLM", "1500.00000 mPLM", "1500")]
|
||||
|
||||
@@ -120,7 +120,7 @@ public class WalletLoaderTests
|
||||
var (doc, accountSeed) = WalletLoader.NewFromMnemonic(
|
||||
ValidMnemonic, null, ScriptKind.NativeSegwit, ChainProfiles.Mainnet);
|
||||
|
||||
// Crea documento watch-only rimuovendo la mnemonica
|
||||
// Build a watch-only document by removing the mnemonic.
|
||||
var docWo = new WalletDocument
|
||||
{
|
||||
Network = doc.Network,
|
||||
|
||||
Reference in New Issue
Block a user