Skip to content

Commit

Permalink
fix features parse + encode to base32
Browse files Browse the repository at this point in the history
  • Loading branch information
StringNick committed Oct 1, 2024
1 parent e364094 commit 04cb5a5
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 36 deletions.
21 changes: 13 additions & 8 deletions src/lightning_invoices/builder.zig
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,18 @@ pub fn setMinFinalCltvExpiryDelta(self: *InvoiceBuilder, delta: u64) !void {

/// Sets the payment secret and relevant features.
pub fn setPaymentSecret(self: *InvoiceBuilder, gpa: std.mem.Allocator, payment_secret: PaymentSecret) !void {
_ = gpa; // autofix
self.secret_flag = true;

var found_features = false;
for (self.tagged_fields.items) |*f| {
switch (f.*) {
.features => |*field| {
_ = field; // autofix
found_features = true;
try field.set(Features.tlv_onion_payload_required);
try field.set(Features.payment_addr_required);
// TODO set after
// try field.set(Features.tlv_onion_payload_required);
// try field.set(Features.payment_addr_required);
},
else => continue,
}
Expand All @@ -197,14 +200,16 @@ pub fn setPaymentSecret(self: *InvoiceBuilder, gpa: std.mem.Allocator, payment_s
self.tagged_fields.appendAssumeCapacity(.{ .payment_secret = payment_secret });

if (!found_features) {
var features = Features{
.flags = std.AutoHashMap(Features.FeatureBit, void).init(gpa),
};
// TODO implement features
// var features = Features{
// .flags = std.AutoHashMap(Features.FeatureBit, void).init(gpa),
// ._flags = undefined,
// };

try features.set(Features.tlv_onion_payload_required);
try features.set(Features.payment_addr_required);
// try features.set(Features.tlv_onion_payload_required);
// try features.set(Features.payment_addr_required);

self.tagged_fields.appendAssumeCapacity(.{ .features = features });
// self.tagged_fields.appendAssumeCapacity(.{ .features = features });
}
}

Expand Down
113 changes: 90 additions & 23 deletions src/lightning_invoices/features.zig
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@
//! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md
const std = @import("std");
const bech32 = @import("bitcoin-primitives").bech32;
const ser = @import("ser.zig");
const invoice = @import("invoice.zig");

const Writer = ser.Writer;
pub const FeatureBit = u16;

const Features = @This();
Expand Down Expand Up @@ -336,42 +339,106 @@ pub inline fn isFeatureRequired(b: FeatureBit) bool {
///
/// This is not exported to bindings users as we map the concrete feature types below directly instead
/// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
flags: std.AutoHashMap(FeatureBit, void),
flags: std.ArrayList(u8),

pub fn set(self: *Features, b: FeatureBit) !void {
try self.flags.put(b, {});
}
// pub fn set(self: *Features, b: FeatureBit) !void {
// try self.flags.put(b, {});
// }

pub fn deinit(self: *Features) void {
self.flags.deinit();
}

pub fn base32Len(self: *const Features) usize {
// its hack (use allocator from features)
var writer = std.ArrayList(u5).init(self.flags.allocator);
defer writer.deinit();

var w = Writer.init(&writer);

// TODO: rewrite to fix em
self.writeBase32(&w) catch unreachable;

return writer.items.len;
}

pub fn writeBase32(self: *const Features, writer: *const Writer) !void {
// Explanation for the "4": the normal way to round up when dividing is to add the divisor
// minus one before dividing
const length_u5s: usize = (self.flags.items.len * 8 + 4) / 5;

var res_u5s = try std.ArrayList(u5).initCapacity(self.flags.allocator, length_u5s);
defer res_u5s.deinit();

res_u5s.appendNTimesAssumeCapacity(0, length_u5s);

for (self.flags.items, 0..) |byte, byte_idx| {
const bit_pos_from_left_0_indexed = byte_idx * 8;
const new_u5_idx = length_u5s - @as(usize, @intCast(bit_pos_from_left_0_indexed / 5)) - 1;
const new_bit_pos = bit_pos_from_left_0_indexed % 5;
const shifted_chunk_u16 = std.math.shl(u16, byte, new_bit_pos);
const curr_u5_as_u8: u8 = @intCast(res_u5s.items[new_u5_idx]);
res_u5s.items[new_u5_idx] =
@intCast(curr_u5_as_u8 | @as(u8, @intCast((shifted_chunk_u16 & 0x001f))));

if (new_u5_idx > 0) {
const _curr_u5_as_u8: u8 = res_u5s.items[new_u5_idx - 1];
res_u5s.items[new_u5_idx - 1] = @intCast(_curr_u5_as_u8 | @as(u8, @intCast((shifted_chunk_u16 >> 5) & 0x001f)));
}

if (new_u5_idx > 1) {
const _curr_u5_as_u8: u8 = res_u5s.items[new_u5_idx - 2];
res_u5s.items[new_u5_idx - 2] =
@intCast(_curr_u5_as_u8 | @as(u8, @intCast(((shifted_chunk_u16 >> 10) & 0x001f))));
}
}

// Trim the highest feature bits.
while (res_u5s.items.len != 0 and res_u5s.items[0] == 0) {
_ = res_u5s.orderedRemove(0);
}

try writer.write(res_u5s.items);
}

pub fn fromBase32(gpa: std.mem.Allocator, data: []const u5) !Features {
_ = data; // autofix
// const field_data = try bech32.arrayListFromBase32(gpa, data);
// defer field_data.deinit();
// Explanation for the "7": the normal way to round up when dividing is to add the divisor
// minus one before dividing
const length_bytes = (data.len * 5 + 7) / 8;

var res_bytes = try std.ArrayList(u8).initCapacity(gpa, length_bytes);
errdefer res_bytes.deinit();

// const width: usize = 5;
res_bytes.appendNTimesAssumeCapacity(0, length_bytes);

// var flags = std.AutoHashMap(FeatureBit, void).init(gpa);
// errdefer flags.deinit();
for (data, 0..) |chunk, u5_idx| {
const bit_pos_from_right_0_indexed = (data.len - u5_idx - 1) * 5;
const new_byte_idx = (bit_pos_from_right_0_indexed / 8);
const new_bit_pos = bit_pos_from_right_0_indexed % 8;
const chunk_u16 = @as(u16, chunk);

// // Set feature bits from parsed data.
// const bits_number = data.len * width;
// for (0..bits_number) |i| {
// const byte_index = i / width;
// const bit_index = i % width;
res_bytes.items[new_byte_idx] |= @intCast(std.math.shl(u16, chunk_u16, new_bit_pos) & 0xff);

// if ((std.math.shl(u8, data[data.len - byte_index - 1], bit_index)) & 1 == 1) {
// try flags.put(@truncate(i), {});
// }
// }
if (new_byte_idx != length_bytes - 1) {
res_bytes.items[new_byte_idx + 1] |= @intCast((std.math.shr(u16, chunk_u16, 8 - new_bit_pos)) & 0xff);
}
}

// return .{
// .flags = flags,
// };
// Trim the highest feature bits.
while (res_bytes.items.len != 0 and res_bytes.items[res_bytes.items.len - 1] == 0) {
_ = res_bytes.pop();
}

return .{
.flags = std.AutoHashMap(FeatureBit, void).init(gpa),
.flags = res_bytes,
};
}

test "encode/decode" {

// { 0, 65, 2, 2 }, data { 1, 0, 4, 16, 8, 0 }
var f = try Features.fromBase32(std.testing.allocator, &.{ 1, 0, 4, 16, 8, 0 });
defer f.deinit();

try std.testing.expectEqualSlices(u8, f.flags.items, &.{ 0, 65, 2, 2 });
}
14 changes: 9 additions & 5 deletions src/lightning_invoices/invoice.zig
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ pub const RawTaggedField = union(enum) {
}
};

fn calculateBase32Len(size: usize) usize {
pub inline fn calculateBase32Len(size: usize) usize {
const bits = size * 8;

return if (bits % 5 == 0)
Expand Down Expand Up @@ -1020,10 +1020,9 @@ pub const TaggedField = union(enum) {
.payment_metadata => |pm| {
try write_tagged_field(writer, constants.TAG_PAYMENT_METADATA, pm.items);
},
// TODO implement other
// features: Features,

else => {},
.features => |f| {
try write_tagged_field(writer, constants.TAG_FEATURES, f);
},
}
}

Expand Down Expand Up @@ -1306,6 +1305,11 @@ test "ln invoice" {

var inv = try Bolt11Invoice.fromStr(std.testing.allocator, str);
defer inv.deinit();

const str_2 = try inv.signed_invoice.toStrAlloc(std.testing.allocator);
defer std.testing.allocator.free(str_2);

try std.testing.expectEqualStrings(str, str_2);
}

test {
Expand Down

0 comments on commit 04cb5a5

Please sign in to comment.