Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 148 additions & 19 deletions src/signature/native/scheme.zig
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,101 @@ pub const HashSubTree = struct {
}
};

/// Helper function to deserialize HashSubTree from leansig SSZ format
fn deserializeHashSubTree(allocator: std.mem.Allocator, serialized: []const u8) !*HashSubTree {
if (serialized.len < 20) return error.InvalidLength;

var offset: usize = 0;

// Decode depth (u64)
const depth = std.mem.readInt(u64, serialized[offset .. offset + 8][0..8], .little);
offset += 8;

// Decode lowest_layer (u64)
const lowest_layer = std.mem.readInt(u64, serialized[offset .. offset + 8][0..8], .little);
offset += 8;
_ = lowest_layer;
_ = depth;

// Decode layers_offset (u32)
const layers_offset = std.mem.readInt(u32, serialized[offset .. offset + 4][0..4], .little);
offset += 4;

// Layers array is a Vec<PaddedLayer>
const layers_data_start = @as(usize, layers_offset);
if (layers_data_start > serialized.len) return error.InvalidOffset;

const layers_data = serialized[layers_data_start..];

// Count number of layer offsets
const first_layer_offset = std.mem.readInt(u32, layers_data[0..4], .little);
const num_layers = first_layer_offset / 4;

// Allocate layers array
const layers = try allocator.alloc(PaddedLayer, num_layers);
errdefer {
for (layers) |layer| {
allocator.free(layer.nodes);
}
allocator.free(layers);
}

// Deserialize each layer
for (0..num_layers) |i| {
const layer_rel_offset = std.mem.readInt(u32, layers_data[i * 4 ..][0..4], .little);
const layer_start = layers_data_start + layer_rel_offset;

// Determine layer end
const layer_end = if (i + 1 < num_layers) blk: {
const next_offset = std.mem.readInt(u32, layers_data[(i + 1) * 4 ..][0..4], .little);
break :blk layers_data_start + next_offset;
} else serialized.len;

const layer_bytes = serialized[layer_start..layer_end];
layers[i] = try deserializePaddedLayer(allocator, layer_bytes);
}

// Extract root from the last layer's last node
const root_value = if (layers.len > 0 and layers[layers.len - 1].nodes.len > 0)
layers[layers.len - 1].nodes[layers[layers.len - 1].nodes.len - 1]
else
[_]FieldElement{FieldElement{ .value = 0 }} ** 8;

return try HashSubTree.initWithLayers(allocator, root_value, layers);
}

/// Helper function to deserialize PaddedLayer from leansig SSZ format
fn deserializePaddedLayer(allocator: std.mem.Allocator, serialized: []const u8) !PaddedLayer {
if (serialized.len < 12) return error.InvalidLength;

// Decode start_index (u64)
const start_index = std.mem.readInt(u64, serialized[0..8], .little);

// Decode nodes_offset (u32)
const nodes_offset = std.mem.readInt(u32, serialized[8..12], .little);

// Nodes array starts at nodes_offset
const nodes_data = serialized[nodes_offset..];

// Each node is 8 x u32 = 32 bytes
const num_nodes = nodes_data.len / 32;
const nodes = try allocator.alloc([8]FieldElement, num_nodes);
errdefer allocator.free(nodes);

// Deserialize nodes
for (0..num_nodes) |i| {
for (0..8) |j| {
const val = std.mem.readInt(u32, nodes_data[i * 32 + j * 4 .. i * 32 + j * 4 + 4][0..4], .little);
nodes[i][j] = FieldElement.fromCanonical(val);
}
}

return PaddedLayer{
.start_index = @intCast(start_index),
.nodes = nodes,
};
}

const CACHE_MAGIC = @as(u32, 0x42544331); // "BTC1"
const CACHE_VERSION: u8 = 2;

Expand Down Expand Up @@ -1215,6 +1310,9 @@ pub const GeneralizedXMSSSecretKey = struct {

// SSZ serialization methods
pub fn sszEncode(self: *const GeneralizedXMSSSecretKey, l: *std.ArrayList(u8)) !void {
// Note: This is a simplified encoding that doesn't include trees (for compatibility with existing code)
// For full leansig-compatible encoding with trees, use a separate method

// Encode prf_key (32 bytes, fixed-size)
try ssz.serialize([32]u8, self.prf_key, l);

Expand All @@ -1234,41 +1332,72 @@ pub const GeneralizedXMSSSecretKey = struct {

pub fn sszDecode(serialized: []const u8, out: *GeneralizedXMSSSecretKey, allocator: ?std.mem.Allocator) !void {
const alloc = allocator orelse return error.AllocatorRequired;
_ = alloc; // Note: Secret key requires trees which are not serialized

if (serialized.len < 88) return error.InvalidLength;

var offset: usize = 0;

// Decode prf_key (32 bytes)
if (serialized.len < offset + 32) return error.InvalidLength;
var prf_key: [32]u8 = undefined;
try ssz.deserialize([32]u8, serialized[offset .. offset + 32], &prf_key, null);
@memcpy(&prf_key, serialized[offset .. offset + 32]);
offset += 32;

// Decode parameter (20 bytes for 5 u32s)
// CRITICAL FIX: ssz.deserialize doesn't work correctly for fixed-size arrays
// Deserialize each u32 individually instead
if (serialized.len < offset + 20) return error.InvalidLength;
var param_canonical: [5]u32 = undefined;
var param_offset = offset;
for (0..5) |i| {
if (serialized.len < param_offset + 4) return error.InvalidLength;
var val: u32 = undefined;
try ssz.deserialize(u32, serialized[param_offset .. param_offset + 4], &val, null);
param_canonical[i] = val;
param_offset += 4;
param_canonical[i] = std.mem.readInt(u32, serialized[offset .. offset + 4][0..4], .little);
offset += 4;
}
offset += 20;
var parameter: [5]FieldElement = undefined;
for (param_canonical, 0..) |val, i| {
parameter[i] = FieldElement.fromCanonical(val);
}

// Validate remaining bytes exist (activation_epoch + num_active_epochs = 16 bytes)
// Note: We don't decode these fields since trees are not serialized and can't be reconstructed
// The caller must call keyGen to reconstruct the full secret key with trees
if (serialized.len < offset + 16) return error.InvalidLength;
_ = out;
return error.SecretKeyRequiresKeyGen; // Indicate that keyGen is needed
// Decode activation_epoch (u64)
const activation_epoch = std.mem.readInt(u64, serialized[offset .. offset + 8][0..8], .little);
offset += 8;

// Decode num_active_epochs (u64)
const num_active_epochs = std.mem.readInt(u64, serialized[offset .. offset + 8][0..8], .little);
offset += 8;

// Decode offsets for variable fields
const top_tree_offset = std.mem.readInt(u32, serialized[offset .. offset + 4][0..4], .little);
offset += 4;

const left_bottom_tree_index = std.mem.readInt(u64, serialized[offset .. offset + 8][0..8], .little);
offset += 8;

const left_bottom_tree_offset = std.mem.readInt(u32, serialized[offset .. offset + 4][0..4], .little);
offset += 4;

const right_bottom_tree_offset = std.mem.readInt(u32, serialized[offset .. offset + 4][0..4], .little);
offset += 4;

// Now offset should be 88, which matches top_tree_offset
if (offset != top_tree_offset) return error.InvalidSSZStructure;

// Deserialize top_tree
const top_tree = try deserializeHashSubTree(alloc, serialized[top_tree_offset..left_bottom_tree_offset]);

// Deserialize left_bottom_tree
const left_bottom_tree = try deserializeHashSubTree(alloc, serialized[left_bottom_tree_offset..right_bottom_tree_offset]);

// Deserialize right_bottom_tree
const right_bottom_tree = try deserializeHashSubTree(alloc, serialized[right_bottom_tree_offset..]);

// Initialize the secret key
out.* = GeneralizedXMSSSecretKey{
.prf_key = prf_key,
.parameter = parameter,
.activation_epoch = @intCast(activation_epoch),
.num_active_epochs = @intCast(num_active_epochs),
.top_tree = top_tree,
.left_bottom_tree_index = @intCast(left_bottom_tree_index),
.left_bottom_tree = left_bottom_tree,
.right_bottom_tree = right_bottom_tree,
.allocator = alloc,
};
}

pub fn isFixedSizeObject(comptime T: type) bool {
Expand Down
Loading