145 lines
5.0 KiB
Zig
145 lines
5.0 KiB
Zig
//! Incremental Merkle Tree implementation for OPQ Manifests.
|
|
//!
|
|
//! Provides O(log n) updates and O(log n) inclusion proofs.
|
|
//! Uses Blake3 for hashing to align with the rest of the SDK.
|
|
|
|
const std = @import("std");
|
|
|
|
pub const MerkleTree = struct {
|
|
allocator: std.mem.Allocator,
|
|
leaves: std.ArrayListUnmanaged([32]u8),
|
|
|
|
pub fn init(allocator: std.mem.Allocator) MerkleTree {
|
|
return .{
|
|
.allocator = allocator,
|
|
.leaves = .{},
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *MerkleTree) void {
|
|
self.leaves.deinit(self.allocator);
|
|
}
|
|
|
|
pub fn insert(self: *MerkleTree, leaf: [32]u8) !void {
|
|
try self.leaves.append(self.allocator, leaf);
|
|
}
|
|
|
|
/// Calculate the root of the Merkle Tree
|
|
pub fn getRoot(self: *const MerkleTree) [32]u8 {
|
|
if (self.leaves.items.len == 0) return [_]u8{0} ** 32;
|
|
if (self.leaves.items.len == 1) return self.leaves.items[0];
|
|
|
|
// This is a naive implementation for now.
|
|
// For production, we'd want an incremental tree that doesn't recompute everything.
|
|
var current_level = std.ArrayList([32]u8).empty;
|
|
defer current_level.deinit(self.allocator);
|
|
current_level.appendSlice(self.allocator, self.leaves.items) catch return [_]u8{0} ** 32;
|
|
|
|
while (current_level.items.len > 1) {
|
|
var next_level = std.ArrayList([32]u8).empty;
|
|
var i: usize = 0;
|
|
while (i < current_level.items.len) : (i += 2) {
|
|
const left = current_level.items[i];
|
|
const right = if (i + 1 < current_level.items.len) current_level.items[i + 1] else left;
|
|
|
|
var hasher = std.crypto.hash.Blake3.init(.{});
|
|
hasher.update(&left);
|
|
hasher.update(&right);
|
|
var out: [32]u8 = undefined;
|
|
hasher.final(&out);
|
|
next_level.append(self.allocator, out) catch break;
|
|
}
|
|
current_level.deinit(self.allocator);
|
|
current_level = next_level;
|
|
}
|
|
|
|
return current_level.items[0];
|
|
}
|
|
|
|
/// Generate an inclusion proof for the leaf at index
|
|
pub fn getProof(self: *const MerkleTree, index: usize) ![][32]u8 {
|
|
if (index >= self.leaves.items.len) return error.IndexOutOfBounds;
|
|
|
|
var proof = std.ArrayList([32]u8).empty;
|
|
errdefer proof.deinit(self.allocator);
|
|
|
|
var current_level = std.ArrayList([32]u8).empty;
|
|
defer current_level.deinit(self.allocator);
|
|
try current_level.appendSlice(self.allocator, self.leaves.items);
|
|
|
|
var current_index = index;
|
|
while (current_level.items.len > 1) {
|
|
const sibling_index = if (current_index % 2 == 0)
|
|
@min(current_index + 1, current_level.items.len - 1)
|
|
else
|
|
current_index - 1;
|
|
|
|
try proof.append(self.allocator, current_level.items[sibling_index]);
|
|
|
|
var next_level = std.ArrayList([32]u8).empty;
|
|
var i: usize = 0;
|
|
while (i < current_level.items.len) : (i += 2) {
|
|
const left = current_level.items[i];
|
|
const right = if (i + 1 < current_level.items.len) current_level.items[i + 1] else left;
|
|
|
|
var hasher = std.crypto.hash.Blake3.init(.{});
|
|
hasher.update(&left);
|
|
hasher.update(&right);
|
|
var out: [32]u8 = undefined;
|
|
hasher.final(&out);
|
|
try next_level.append(self.allocator, out);
|
|
}
|
|
current_level.deinit(self.allocator);
|
|
current_level = next_level;
|
|
current_index /= 2;
|
|
}
|
|
|
|
return proof.toOwnedSlice(self.allocator);
|
|
}
|
|
|
|
pub fn verify(root: [32]u8, leaf: [32]u8, index: usize, proof: [][32]u8) bool {
|
|
var current_hash = leaf;
|
|
var current_index = index;
|
|
for (proof) |sibling| {
|
|
var hasher = std.crypto.hash.Blake3.init(.{});
|
|
if (current_index % 2 == 0) {
|
|
hasher.update(¤t_hash);
|
|
hasher.update(&sibling);
|
|
} else {
|
|
hasher.update(&sibling);
|
|
hasher.update(¤t_hash);
|
|
}
|
|
hasher.final(¤t_hash);
|
|
current_index /= 2;
|
|
}
|
|
return std.mem.eql(u8, ¤t_hash, &root);
|
|
}
|
|
};
|
|
|
|
test "MerkleTree: root and proof" {
|
|
const allocator = std.testing.allocator;
|
|
var tree = MerkleTree.init(allocator);
|
|
defer tree.deinit();
|
|
|
|
const h1 = [_]u8{1} ** 32;
|
|
const h2 = [_]u8{2} ** 32;
|
|
const h3 = [_]u8{3} ** 32;
|
|
|
|
try tree.insert(h1);
|
|
try tree.insert(h2);
|
|
try tree.insert(h3);
|
|
|
|
const root = tree.getRoot();
|
|
|
|
// Manual verification of root:
|
|
// next1 = Blake3(h1, h2)
|
|
// next2 = Blake3(h3, h3)
|
|
// root = Blake3(next1, next2)
|
|
|
|
const proof = try tree.getProof(0); // Proof for h1
|
|
defer allocator.free(proof);
|
|
|
|
try std.testing.expect(MerkleTree.verify(root, h1, 0, proof));
|
|
try std.testing.expect(!MerkleTree.verify(root, h2, 0, proof));
|
|
}
|