Skip to content

Commit 5d0435b

Browse files
committed
Make init message serialization legacy-compatible
Older lightning clients expect every feature flags to appear only in either the global or local feature set of the init message. This commit makes DNL compatible with these clients while still supporting modern clients.
1 parent 7daeb43 commit 5d0435b

3 files changed

Lines changed: 81 additions & 33 deletions

File tree

src/DotNetLightning.Core/Serialize/Features.fs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ module internal Feature =
8989
|> Map.add (Feature.BasicMultiPartPayment) ([Feature.PaymentSecret])
9090
|> Map.add (Feature.PaymentSecret) ([Feature.VariableLengthOnion])
9191

92-
let private isFeatureOn(features: BitArray) (bit: int) =
92+
let isFeatureOn(features: BitArray) (bit: int) =
9393
(features.Length > bit) && features.Reverse().[bit]
9494

9595
let hasFeature(features: BitArray) (f: Feature) (support: FeaturesSupport option) =
@@ -173,14 +173,12 @@ module internal Feature =
173173
/// Uses regular class instead of F# type for caching byte[] representation
174174
[<StructuredFormatDisplay("{PrettyPrint}")>]
175175
type FeatureBit private (bitArray) =
176-
let mutable bytes = null
177176
member val BitArray: BitArray = bitArray with get, set
178177
member this.ByteArray
179178
with get() =
180-
if isNull bytes then
181-
bytes <- this.BitArray.ToByteArray()
182-
bytes
183-
and set(v: byte[]) = bytes <- v
179+
bitArray.ToByteArray()
180+
and set(bytes: byte[]) =
181+
this.BitArray <- BitArray.FromBytes(bytes)
184182
new() =
185183
FeatureBit(
186184
let b: bool array = [||]
@@ -199,12 +197,8 @@ type FeatureBit private (bitArray) =
199197
}
200198
static member Zero = FeatureBit()
201199
static member TryCreate(bytes: byte[]) =
202-
result {
203-
let! fb = FeatureBit.TryCreate(BitArray.FromBytes(bytes))
204-
fb.ByteArray <- bytes
205-
return fb
206-
}
207-
200+
FeatureBit.TryCreate(BitArray.FromBytes(bytes))
201+
208202
static member TryCreate(v: int64) =
209203
BitArray.FromInt64(v) |> FeatureBit.TryCreate
210204

@@ -233,6 +227,29 @@ type FeatureBit private (bitArray) =
233227
override this.ToString() =
234228
this.BitArray.PrintBits()
235229

230+
member this.SetFeatureBit(index: int, on: bool): unit =
231+
let length = this.BitArray.Length
232+
if length <= index then
233+
this.BitArray.Length <- index + 1
234+
235+
//this.BitArray.RightShift(index - length + 1)
236+
237+
// NOTE: Calling RightShift gives me:
238+
// "The field, constructor or member 'RightShift' is not defined."
239+
// So I just re-implement it here
240+
for i in (length - 1) .. -1 .. 0 do
241+
this.BitArray.[i + index - length + 1] <- this.BitArray.[i]
242+
243+
// NOTE: this probably wouldn't be necessary if we were using
244+
// RightShift, but the dotnet docs don't actualy specify that
245+
// RightShift sets the leading bits to zero.
246+
for i in 0 .. (index - length) do
247+
this.BitArray.[i] <- false
248+
this.BitArray.[this.BitArray.Length - index - 1] <- on
249+
250+
member this.GetFeatureBit(index: int): bool =
251+
Feature.isFeatureOn this.BitArray index
252+
236253
member this.HasFeature(f, ?featureType) =
237254
Feature.hasFeature this.BitArray (f) (featureType)
238255

src/DotNetLightning.Core/Serialize/Msgs/Msgs.fs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ open NBitcoin
99
open DotNetLightning.Utils
1010
open DotNetLightning.Utils.Primitives
1111
open DotNetLightning.Utils.OnionError
12+
open DotNetLightning.Core.Utils.Extensions
1213

1314
open DotNetLightning.Serialize
1415

@@ -375,7 +376,8 @@ type Init =
375376
interface ISetupMsg
376377
interface ILightningSerializable<Init> with
377378
member this.Deserialize(ls: LightningReaderStream) =
378-
// For backwards compatibility reason, we must consider legacy `global features` section. (see bolt 1)
379+
// For backwards compatibility reason, we must consider legacy
380+
// `global features` section. (see bolt 1)
379381
let globalFeatures = ls.ReadWithLen()
380382
let localFeatures = ls.ReadWithLen()
381383
let oredFeatures =
@@ -396,24 +398,30 @@ type Init =
396398
oredFeatures
397399
this.Features <- oredFeatures |> FeatureBit.CreateUnsafe
398400
this.TLVStream <- ls.ReadTLVStream() |> Array.map(InitTLV.FromGenericTLV)
401+
399402
member this.Serialize(ls) =
400-
// Trim any leading zero bytes from the from of the local features
403+
// For legacy compatiblity we treat the VariableLengthOnion feature specially.
404+
// Older versions of the lightning protocol spec had a
405+
// distinction between global and local features, of which
406+
// VariableLengthOnion was the only global feature. Although
407+
// this distinction has since been removed, older clients still
408+
// expect VariableLengthOnion to appear only in the
409+
// global_features field of an init message and other features
410+
// to appear only in the local_features field. This is still
411+
// compatible with newer clients since those clients simply OR
412+
// the two feature fields.
401413
let localFeatures =
402-
let features = this.Features.ToByteArray()
403-
let startIndex =
404-
match Array.tryFindIndex (fun (b: byte) -> b <> 0uy) features with
405-
| Some index -> index
406-
| None -> features.Length
407-
features.[startIndex..]
408-
// For legacy compatibility, the last (lowest) 13 bits get
409-
// duplicated in the globalFeatures array.
414+
let localFeatures = this.Features
415+
localFeatures.SetFeatureBit(Feature.VariableLengthOnion.MandatoryBitPosition, false)
416+
localFeatures.SetFeatureBit(Feature.VariableLengthOnion.OptionalBitPosition, false)
417+
localFeatures.ToByteArray()
410418
let globalFeatures =
411-
if localFeatures.Length < 2 then
412-
localFeatures
413-
else
414-
let features = localFeatures.[(localFeatures.Length - 2)..]
415-
features.[0] <- features.[0] &&& 0b00111111uy
416-
features
419+
let globalFeatures = FeatureBit.Zero
420+
if this.Features.GetFeatureBit(Feature.VariableLengthOnion.MandatoryBitPosition) then
421+
globalFeatures.SetFeatureBit(Feature.VariableLengthOnion.MandatoryBitPosition, true)
422+
if this.Features.GetFeatureBit(Feature.VariableLengthOnion.OptionalBitPosition) then
423+
globalFeatures.SetFeatureBit(Feature.VariableLengthOnion.OptionalBitPosition, true)
424+
globalFeatures.ToByteArray()
417425
ls.WriteWithLen(globalFeatures)
418426
ls.WriteWithLen(localFeatures)
419427
ls.WriteTLVStream(this.TLVStream |> Array.map(fun tlv -> tlv.ToGenericTLV()))

src/DotNetLightning.Core/Utils/Extensions.fs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,34 @@ type BitArrayExtensions() =
101101
type System.Collections.BitArray with
102102
member this.ToByteArray() =
103103
if this.Length = 0 then [||] else
104-
let ret: byte[] = Array.zeroCreate (((this.Length - 1) / 8) + 1)
105-
let boolArray: bool[] = this.Reverse()
106-
let t = BitArray(boolArray)
107-
t.CopyTo(ret, 0)
108-
ret |> Array.rev
104+
105+
let leadingZeros =
106+
match (Seq.tryFindIndex (fun b -> b) (Seq.cast this)) with
107+
| Some i -> i
108+
| None -> this.Length
109+
let trueLength = this.Length - leadingZeros
110+
let desiredLength = ((trueLength + 7) / 8) * 8
111+
let difference = desiredLength - this.Length
112+
let bitArray =
113+
if difference < 0 then
114+
// Drop zeroes from the front of the array until we have a multiple of 8 bits
115+
let shortenedBitArray = BitArray(desiredLength)
116+
for i in 0 .. (desiredLength - 1) do
117+
shortenedBitArray.[i] <- this.[i - difference]
118+
shortenedBitArray
119+
else if difference > 0 then
120+
// Push zeroes to the front of the array until we have a multiple of 8 bits
121+
let lengthenedBitArray = BitArray(desiredLength)
122+
for i in 0 .. (this.Length - 1) do
123+
lengthenedBitArray.[i + difference] <- this.[i]
124+
lengthenedBitArray
125+
else
126+
this
127+
128+
// Copy the bit array to a byte array, then flip the bytes.
129+
let byteArray: byte[] = Array.zeroCreate(desiredLength / 8)
130+
bitArray.CopyTo(byteArray, 0)
131+
byteArray |> Array.map (fun b -> b.FlipBit())
109132

110133
static member From5BitEncoding(b: byte[]) =
111134
let bitArray = System.Collections.BitArray(b.Length * 5)

0 commit comments

Comments
 (0)