diff --git a/NBitcoin.Tests/bip32_tests.cs b/NBitcoin.Tests/bip32_tests.cs index b7f7d20d3..4a122e087 100644 --- a/NBitcoin.Tests/bip32_tests.cs +++ b/NBitcoin.Tests/bip32_tests.cs @@ -338,5 +338,42 @@ public void KeyPathShouldNotParseBIP32Overflow() Assert.Equal(0x80000000U, uint.Parse("2147483648")); Assert.Throws(() => KeyPath.Parse("/2147483648")); } + + [Fact] + [Trait("UnitTest", "UnitTest")] + public void ShouldRejectInvalidExtendedKeys_BIP32TestVector5() + { + // BIP-32 Test Vector 5: These vectors test that invalid extended keys are recognized as invalid + + // pubkey version / prvkey mismatch + Assert.Throws(() => ExtKey.Parse("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm", Network.Main)); + // prvkey version / pubkey mismatch + Assert.Throws(() => ExtPubKey.Parse("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH", Network.Main)); + // invalid pubkey prefix 04 + Assert.Throws(() => ExtPubKey.Parse("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn", Network.Main)); + // invalid prvkey prefix 04 + Assert.Throws(() => ExtKey.Parse("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ", Network.Main)); + // invalid pubkey prefix 01 + Assert.Throws(() => ExtPubKey.Parse("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4", Network.Main)); + // invalid prvkey prefix 01 + Assert.Throws(() => ExtKey.Parse("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J", Network.Main)); + // zero depth with non-zero parent fingerprint + Assert.Throws(() => ExtKey.Parse("xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv", Network.Main)); + Assert.Throws(() => ExtPubKey.Parse("xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ", Network.Main)); + // zero depth with non-zero index + Assert.Throws(() => ExtKey.Parse("xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN", Network.Main)); + Assert.Throws(() => ExtPubKey.Parse("xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8", Network.Main)); + // unknown extended key version + Assert.Throws(() => ExtKey.Parse("DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4", Network.Main)); + Assert.Throws(() => ExtPubKey.Parse("DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9", Network.Main)); + // private key 0 not in 1..n-1 + Assert.Throws(() => ExtKey.Parse("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx", Network.Main)); + // private key n not in 1..n-1 + Assert.Throws(() => ExtKey.Parse("xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G", Network.Main)); + // invalid pubkey 020000000000000000000000000000000000000000000000000000000000000007 + Assert.Throws(() => ExtPubKey.Parse("xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY", Network.Main)); + // invalid checksum + Assert.Throws(() => ExtKey.Parse("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL", Network.Main)); + } } } diff --git a/NBitcoin/BIP32/ExtKey.cs b/NBitcoin/BIP32/ExtKey.cs index c4612bcfa..f182ee44e 100644 --- a/NBitcoin/BIP32/ExtKey.cs +++ b/NBitcoin/BIP32/ExtKey.cs @@ -237,6 +237,8 @@ private ExtKey(byte[] bytes, bool isSeed) i += 4; nChild = Utils.ToUInt32(bytes, i, false); i += 4; + if (nDepth == 0 && (parentFingerprint != default || nChild != 0)) + throw new ArgumentException("Invalid ExtKey: Master key (depth 0) must have zero parent fingerprint and zero child number"); vchChainCode = new byte[32]; Array.Copy(bytes, i, vchChainCode, 0, 32); i += 32; @@ -277,6 +279,8 @@ private ExtKey(ReadOnlySpan bytes, bool isSeed) i += 4; nChild = Utils.ToUInt32(bytes.Slice(i, 4), false); i += 4; + if (nDepth == 0 && (parentFingerprint != default || nChild != 0)) + throw new ArgumentException("Invalid ExtKey: Master key (depth 0) must have zero parent fingerprint and zero child number"); vchChainCode = new byte[32]; bytes.Slice(i, 32).CopyTo(vchChainCode); i += 32; diff --git a/NBitcoin/BIP32/ExtPubKey.cs b/NBitcoin/BIP32/ExtPubKey.cs index ae8993ca4..7363a514b 100644 --- a/NBitcoin/BIP32/ExtPubKey.cs +++ b/NBitcoin/BIP32/ExtPubKey.cs @@ -91,6 +91,8 @@ public ExtPubKey(byte[] bytes, int offset, int length) i += 4; nChild = Utils.ToUInt32(bytes, i, false); i += 4; + if (nDepth == 0 && (parentFingerprint != default || nChild != 0)) + throw new ArgumentException("Invalid ExtPubKey: Master key (depth 0) must have zero parent fingerprint and zero child number"); vchChainCode = new byte[32]; Array.Copy(bytes, i, vchChainCode, 0, 32); i += 32; @@ -115,6 +117,8 @@ public ExtPubKey(ReadOnlySpan bytes) i += 4; nChild = Utils.ToUInt32(bytes.Slice(i, 4), false); i += 4; + if (nDepth == 0 && (parentFingerprint != default || nChild != 0)) + throw new ArgumentException("Invalid ExtPubKey: Master key (depth 0) must have zero parent fingerprint and zero child number"); vchChainCode = new byte[32]; bytes.Slice(i, 32).CopyTo(vchChainCode); i += 32;