diff --git a/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs b/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs
index 670adeff..94de8ae2 100644
--- a/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs
+++ b/src/NLightning.Bolt11/Models/TaggedFields/FallbackAddressTaggedField.cs
@@ -84,12 +84,12 @@ public bool IsValid()
/// The BitReader to read from
/// The length of the field
/// The network type
- /// The FallbackAddressTaggedField
- /// Thrown when the address is unknown or invalid
- internal static FallbackAddressTaggedField FromBitReader(BitReader bitReader, short length,
- BitcoinNetwork bitcoinNetwork)
+ /// The FallbackAddressTaggedField, or null if the version is unknown (per BOLT 11, unknown versions should be skipped)
+ /// Thrown when the network is invalid
+ internal static FallbackAddressTaggedField? FromBitReader(BitReader bitReader, short length,
+ BitcoinNetwork bitcoinNetwork)
{
- // Get Address Type
+ // Get Address Type (witness version)
var addressType = bitReader.ReadByteFromBits(5);
var newLength = length - 1;
@@ -102,7 +102,11 @@ internal static FallbackAddressTaggedField FromBitReader(BitReader bitReader, sh
var network = Network.GetNetwork(bitcoinNetwork) ??
throw new ArgumentException("Network is unknown or invalid.", nameof(bitcoinNetwork));
- BitcoinAddress address = addressType switch
+
+ // Per BOLT 11: "MUST skip over `f` fields that use an unknown `version`"
+ // Supported versions: 0 (P2WPKH/P2WSH), 17 (P2PKH), 18 (P2SH)
+ // Unknown versions (1-16 for future witness versions, 19-31 reserved) should be skipped
+ BitcoinAddress? address = addressType switch
{
// Witness P2WPKH
0 when data.Length == 20 => new WitKeyId(data).GetAddress(network),
@@ -112,9 +116,10 @@ internal static FallbackAddressTaggedField FromBitReader(BitReader bitReader, sh
17 => new KeyId(data).GetAddress(network),
// P2SH
18 => new ScriptId(data).GetAddress(network),
- _ => throw new ArgumentException("Address is unknown or invalid.", nameof(bitReader))
+ // Unknown version - skip per BOLT 11 spec
+ _ => null
};
- return new FallbackAddressTaggedField(address);
+ return address is null ? null : new FallbackAddressTaggedField(address);
}
}
\ No newline at end of file
diff --git a/test/NLightning.Bolt11.Tests/Models/TaggedFields/FallbackAddressTaggedFieldTests.cs b/test/NLightning.Bolt11.Tests/Models/TaggedFields/FallbackAddressTaggedFieldTests.cs
index 3ef0c1dc..1cae2f3d 100644
--- a/test/NLightning.Bolt11.Tests/Models/TaggedFields/FallbackAddressTaggedFieldTests.cs
+++ b/test/NLightning.Bolt11.Tests/Models/TaggedFields/FallbackAddressTaggedFieldTests.cs
@@ -117,6 +117,7 @@ public void FromBitReader_CreatesCorrectlyFromBitReader(string expectedAddress,
var taggedField = FallbackAddressTaggedField.FromBitReader(bitReader, bitLength, BitcoinNetwork.Mainnet);
// Assert
+ Assert.NotNull(taggedField);
Assert.Equal(expectedAddress, taggedField.Value.ToString());
}
@@ -132,15 +133,45 @@ public void IsValid_ReturnsTrue()
Assert.True(taggedField.IsValid());
}
+ [Theory]
+ [InlineData(1)] // Witness v1 (Taproot) - not yet supported
+ [InlineData(2)] // Future witness version
+ [InlineData(16)] // Future witness version
+ [InlineData(19)] // Reserved per BOLT 11
+ [InlineData(20)] // Reserved per BOLT 11
+ [InlineData(31)] // Reserved per BOLT 11 (max 5-bit value)
+ public void FromBitReader_ReturnsNull_ForUnknownVersion(byte version)
+ {
+ // Arrange - Create data with the version in the first 5 bits
+ // Version is stored in 5 bits, followed by 20 bytes (160 bits) of address data
+ // Total: 5 + 160 = 165 bits = 33 5-bit units
+ var data = new byte[21]; // (165 bits + 7) / 8 = 21 bytes
+ data[0] = (byte)(version << 3); // Version in first 5 bits (shifted to align)
+ // Fill remaining with dummy address data
+ for (var i = 1; i < data.Length; i++)
+ data[i] = 0xAB;
+
+ var bitReader = new BitReader(data);
+
+ // Act
+ var result = FallbackAddressTaggedField.FromBitReader(bitReader, 33, BitcoinNetwork.Mainnet);
+
+ // Assert - Per BOLT 11: "MUST skip over `f` fields that use an unknown `version`"
+ Assert.Null(result);
+ }
+
[Fact]
- public void FromBitReader_ThrowsArgumentException_ForInvalidAddressType()
+ public void FromBitReader_ReturnsNull_ForVersion0WithInvalidDataLength()
{
- // Arrange
- var invalidData = new byte[] { 255, 0x00, 0x00, 0x00 };
- var bitReader = new BitReader(invalidData);
+ // Arrange - Version 0 but with 15-byte data (not 20 or 32)
+ // This should return null since it's not a valid P2WPKH (20) or P2WSH (32)
+ var data = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };
+ var bitReader = new BitReader(data);
- // Act & Assert
- Assert.Throws(() => FallbackAddressTaggedField.FromBitReader(
- bitReader, 4, BitcoinNetwork.Mainnet));
+ // Act
+ var result = FallbackAddressTaggedField.FromBitReader(bitReader, 13, BitcoinNetwork.Mainnet);
+
+ // Assert
+ Assert.Null(result);
}
}
\ No newline at end of file
diff --git a/test/NLightning.Integration.Tests/BOLT11/TaggedFields/FallbackAddressTaggedFieldIntegrationTests.cs b/test/NLightning.Integration.Tests/BOLT11/TaggedFields/FallbackAddressTaggedFieldIntegrationTests.cs
index 9e14ad8e..c63eeb5f 100644
--- a/test/NLightning.Integration.Tests/BOLT11/TaggedFields/FallbackAddressTaggedFieldIntegrationTests.cs
+++ b/test/NLightning.Integration.Tests/BOLT11/TaggedFields/FallbackAddressTaggedFieldIntegrationTests.cs
@@ -73,6 +73,7 @@ public void FromBitReader_Reads_From_Beginning_Of_Buffer(BitcoinAddress address)
var parsed = FallbackAddressTaggedField.FromBitReader(reader, length, BitcoinNetwork.Mainnet);
+ Assert.NotNull(parsed);
Assert.Equal(expected.ToString(), parsed.Value.ToString());
}
@@ -86,6 +87,7 @@ public void FromBitReader_Reads_From_Middle_Of_Buffer_Unaligned(BitcoinAddress a
var parsed = FallbackAddressTaggedField.FromBitReader(reader, length, BitcoinNetwork.Mainnet);
+ Assert.NotNull(parsed);
Assert.Equal(expected.ToString(), parsed.Value.ToString());
}
@@ -99,6 +101,7 @@ public void FromBitReader_Reads_From_Middle_Of_Buffer_Aligned(BitcoinAddress add
var parsed = FallbackAddressTaggedField.FromBitReader(reader, length, BitcoinNetwork.Mainnet);
+ Assert.NotNull(parsed);
Assert.Equal(expected.ToString(), parsed.Value.ToString());
}
@@ -112,6 +115,7 @@ public void FromBitReader_Reads_Near_End_Of_Buffer_Unaligned(BitcoinAddress addr
var parsed = FallbackAddressTaggedField.FromBitReader(reader, length, BitcoinNetwork.Mainnet);
+ Assert.NotNull(parsed);
Assert.Equal(expected.ToString(), parsed.Value.ToString());
}
@@ -125,6 +129,7 @@ public void FromBitReader_Reads_Near_End_Of_Buffer_Aligned(BitcoinAddress addres
var parsed = FallbackAddressTaggedField.FromBitReader(reader, length, BitcoinNetwork.Mainnet);
+ Assert.NotNull(parsed);
Assert.Equal(expected.ToString(), parsed.Value.ToString());
}
@@ -138,6 +143,7 @@ public void FromBitReader_Reads_At_End_Of_Buffer(BitcoinAddress address)
var parsed = FallbackAddressTaggedField.FromBitReader(reader, length, BitcoinNetwork.Mainnet);
+ Assert.NotNull(parsed);
Assert.Equal(expected.ToString(), parsed.Value.ToString());
}
}
\ No newline at end of file