c868c17505
Wordlist.AutoDetect lands on NBitcoin's internal "Unknown" language for text resembling no wordlist and throws NotSupportedException instead of failing gracefully — found by fuzzing the restore-mnemonic input. TryParse now treats that (and the same failure mode from the Mnemonic constructor) as "not a valid mnemonic", consistent with every other rejection path.
141 lines
6.6 KiB
C#
141 lines
6.6 KiB
C#
using PalladiumWallet.Core.Crypto;
|
||
|
||
namespace PalladiumWallet.Tests.Crypto;
|
||
|
||
public class Bip39Tests
|
||
{
|
||
// Official Trezor vectors (python-mnemonic/vectors.json), passphrase "TREZOR".
|
||
[Theory]
|
||
[InlineData(
|
||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04")]
|
||
[InlineData(
|
||
"legal winner thank year wave sausage worth useful legal winner thank yellow",
|
||
"2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607")]
|
||
[InlineData(
|
||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
|
||
"bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8")]
|
||
[InlineData(
|
||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
|
||
"dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad")]
|
||
[InlineData(
|
||
"void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold",
|
||
"01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998")]
|
||
public void I_vettori_trezor_producono_il_seed_atteso(string words, string expectedSeedHex)
|
||
{
|
||
Assert.True(Bip39.TryParse(words, out var mnemonic));
|
||
Assert.Equal(expectedSeedHex, Convert.ToHexString(Bip39.ToSeed(mnemonic!, "TREZOR")).ToLowerInvariant());
|
||
}
|
||
|
||
[Fact]
|
||
public void Il_vettore_giapponese_copre_la_normalizzazione_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 = "㍍ガバヴァぱばぐゞちぢ十人十色";
|
||
const string expectedSeed =
|
||
"a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55";
|
||
|
||
Assert.True(Bip39.TryParse(words, out var mnemonic, MnemonicLanguage.Japanese));
|
||
Assert.Equal(expectedSeed, Convert.ToHexString(Bip39.ToSeed(mnemonic!, passphrase)).ToLowerInvariant());
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData(MnemonicLength.Twelve)]
|
||
[InlineData(MnemonicLength.TwentyFour)]
|
||
public void Generate_produce_il_numero_di_parole_richiesto_con_checksum_valido(MnemonicLength length)
|
||
{
|
||
var mnemonic = Bip39.Generate(length);
|
||
Assert.Equal((int)length, mnemonic.Words.Length);
|
||
Assert.True(Bip39.TryParse(mnemonic.ToString(), out _));
|
||
}
|
||
|
||
[Fact]
|
||
public void Checksum_invalido_viene_rifiutato()
|
||
{
|
||
// 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 _));
|
||
}
|
||
|
||
[Fact]
|
||
public void Numero_di_parole_errato_viene_rifiutato()
|
||
{
|
||
var words = string.Join(' ', Enumerable.Repeat("abandon", 12)) + " about";
|
||
Assert.False(Bip39.TryParse(words, out _));
|
||
}
|
||
|
||
[Fact]
|
||
public void La_lingua_viene_riconosciuta_automaticamente()
|
||
{
|
||
var spanish = Bip39.Generate(MnemonicLength.Twelve, MnemonicLanguage.Spanish);
|
||
Assert.True(Bip39.TryParse(spanish.ToString(), out var parsed));
|
||
Assert.Equal(spanish.ToString(), parsed!.ToString());
|
||
}
|
||
|
||
[Fact]
|
||
public void Passphrase_diverse_producono_seed_diversi()
|
||
{
|
||
Assert.True(Bip39.TryParse(
|
||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||
out var mnemonic));
|
||
|
||
var noPass = Bip39.ToSeed(mnemonic!);
|
||
var withPass = Bip39.ToSeed(mnemonic!, "TREZOR");
|
||
var otherPass = Bip39.ToSeed(mnemonic!, "trezor"); // case-sensitive (§4.1)
|
||
|
||
Assert.NotEqual(Convert.ToHexString(noPass), Convert.ToHexString(withPass));
|
||
Assert.NotEqual(Convert.ToHexString(withPass), Convert.ToHexString(otherPass));
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData("")]
|
||
[InlineData(" ")]
|
||
[InlineData(null)]
|
||
public void Testo_vuoto_o_solo_spazi_viene_rifiutato(string? text)
|
||
{
|
||
Assert.False(Bip39.TryParse(text!, out var mnemonic));
|
||
Assert.Null(mnemonic);
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData(MnemonicLanguage.English)]
|
||
[InlineData(MnemonicLanguage.Spanish)]
|
||
[InlineData(MnemonicLanguage.French)]
|
||
[InlineData(MnemonicLanguage.Japanese)]
|
||
[InlineData(MnemonicLanguage.PortugueseBrazil)]
|
||
[InlineData(MnemonicLanguage.ChineseSimplified)]
|
||
[InlineData(MnemonicLanguage.ChineseTraditional)]
|
||
[InlineData(MnemonicLanguage.Czech)]
|
||
public void Ogni_lingua_supportata_genera_e_riparsa_la_propria_mnemonica(MnemonicLanguage language)
|
||
{
|
||
var mnemonic = Bip39.Generate(MnemonicLength.Twelve, language);
|
||
|
||
// Explicit-language parse must always succeed; it also covers the full
|
||
// language→wordlist map (USERGUIDE §2.1 promises these languages on restore).
|
||
Assert.True(Bip39.TryParse(mnemonic.ToString(), out var parsed, language));
|
||
Assert.Equal(mnemonic.ToString(), parsed!.ToString());
|
||
}
|
||
|
||
[Fact]
|
||
public void Una_lingua_fuori_enum_viene_rifiutata()
|
||
{
|
||
Assert.Throws<ArgumentOutOfRangeException>(
|
||
() => Bip39.Generate(MnemonicLength.Twelve, (MnemonicLanguage)99));
|
||
}
|
||
|
||
[Fact]
|
||
public void Testo_che_non_somiglia_a_nessuna_wordlist_viene_rifiutato_senza_eccezioni()
|
||
{
|
||
// NBitcoin's Wordlist.AutoDetect throws NotSupportedException("Unknown")
|
||
// for text resembling no wordlist instead of failing gracefully (found by
|
||
// fuzzing): TryParse must swallow it and return false — this string reaches
|
||
// TryParse straight from the user's restore input.
|
||
Assert.False(Bip39.TryParse("ábÊco ábaco0ábaco ábÊ", out var mnemonic));
|
||
Assert.Null(mnemonic);
|
||
}
|
||
}
|