From e120b8c8a33fd3ff4022be62bbc7ec8c86a3a069 Mon Sep 17 00:00:00 2001 From: Nikolay Zdravkov Date: Thu, 5 Nov 2026 03:20:00 +0200 Subject: [PATCH 1/2] fix: decode BIP38 sequence correctly --- NBitcoin.Tests/BIP38Tests.cs | 3 ++- NBitcoin/BIP38/BitcoinPassphraseCode.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/NBitcoin.Tests/BIP38Tests.cs b/NBitcoin.Tests/BIP38Tests.cs index 8a905c5fe9..e93d017cc5 100644 --- a/NBitcoin.Tests/BIP38Tests.cs +++ b/NBitcoin.Tests/BIP38Tests.cs @@ -199,7 +199,8 @@ public void CanParseLotSequence() var sequence = new LotSequence(1048575, 1024); var bytes = sequence.ToBytes(); var sequence2 = new LotSequence(bytes); - Assert.True(sequence.Lot == sequence2.Lot && sequence.Sequence == sequence.Sequence); + Assert.Equal(sequence.Lot, sequence2.Lot); + Assert.Equal(sequence.Sequence, sequence2.Sequence); } [Fact] diff --git a/NBitcoin/BIP38/BitcoinPassphraseCode.cs b/NBitcoin/BIP38/BitcoinPassphraseCode.cs index 87f10d1a6e..6b6a127e57 100644 --- a/NBitcoin/BIP38/BitcoinPassphraseCode.cs +++ b/NBitcoin/BIP38/BitcoinPassphraseCode.cs @@ -93,7 +93,7 @@ public LotSequence(byte[] bytes) ((uint)_Bytes[3] << 0); _Lot = (int)(lotSequence / 4096); - _Sequence = (int)(lotSequence - _Lot); + _Sequence = (int)(lotSequence % 4096); } private readonly int _Lot; From 2d97ac57e17ea191946a1739968d30288f17d1d8 Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Thu, 5 Nov 2026 12:14:16 +0900 Subject: [PATCH 2/2] fix: allow full BIP38 sequence range --- NBitcoin.Tests/BIP38Tests.cs | 19 ++++++++++++++++--- NBitcoin/BIP38/BitcoinPassphraseCode.cs | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/NBitcoin.Tests/BIP38Tests.cs b/NBitcoin.Tests/BIP38Tests.cs index e93d017cc5..5c764d4663 100644 --- a/NBitcoin.Tests/BIP38Tests.cs +++ b/NBitcoin.Tests/BIP38Tests.cs @@ -192,17 +192,30 @@ private void AssertSequenceEquals(LotSequence expected, LotSequence actual) } - [Fact] + [Theory] + [InlineData(0, 0)] + [InlineData(1048575, 1024)] + [InlineData(1048575, 1025)] + [InlineData(1048575, 4095)] [Trait("UnitTest", "UnitTest")] - public void CanParseLotSequence() + public void CanParseLotSequence(int lot, int sequenceNumber) { - var sequence = new LotSequence(1048575, 1024); + var sequence = new LotSequence(lot, sequenceNumber); var bytes = sequence.ToBytes(); var sequence2 = new LotSequence(bytes); Assert.Equal(sequence.Lot, sequence2.Lot); Assert.Equal(sequence.Sequence, sequence2.Sequence); } + [Theory] + [InlineData(-1)] + [InlineData(4096)] + [Trait("UnitTest", "UnitTest")] + public void RejectsInvalidSequence(int sequence) + { + Assert.Throws(() => new LotSequence(0, sequence)); + } + [Fact] [Trait("UnitTest", "UnitTest")] public void EncryptedSecretECmultiplyNoLotSimple() diff --git a/NBitcoin/BIP38/BitcoinPassphraseCode.cs b/NBitcoin/BIP38/BitcoinPassphraseCode.cs index 6b6a127e57..23ad4612c7 100644 --- a/NBitcoin/BIP38/BitcoinPassphraseCode.cs +++ b/NBitcoin/BIP38/BitcoinPassphraseCode.cs @@ -68,7 +68,7 @@ public LotSequence(int lot, int sequence) { if (lot > 1048575 || lot < 0) throw new ArgumentOutOfRangeException("lot"); - if (sequence > 1024 || sequence < 0) + if (sequence > 4095 || sequence < 0) throw new ArgumentOutOfRangeException("sequence"); _Lot = lot;