Skip to content

Commit 260dc69

Browse files
hyperpolymathclaude
andcommitted
fix(lithoglyph): migrate Zig 0.15.2 API changes across core-zig and gql-dt/bridge
core-zig: - build.zig: wire _crypto_tests (unused const) into test step; rename to crypto_tests - src/crypto.zig: full 0.15.2 migration — fix 3-arg @memcpy → std.mem.writeInt, @ptrCast old syntax → typed *const [N]u8 params, [N]u8{val} → std.mem.zeroes, block.header.encrypted (non-existent field) → flags bitmask checks, @truncate(enum) → @intFromEnum, runtime-sized stack array → caller-owned out_buf, key_bytes.len on many-ptr → loop modulo AES256_KEY_SIZE constant - src/blocks.zig: isEncrypted() — use flag bitmask instead of non-existent .encrypted field - src/crypto_test.zig: [N]u8{0} (1 element) → std.mem.zeroes([N]u8) gql-dt/bridge: - lith_persist.zig: fix test assertions — lith_init returns i32 (not LithStatus), add missing dummy args to lith_is_init(0) and lith_save(0) Test results: core-zig 33/33, ffi/zig 10/10, gql-dt/bridge 15/15. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2bf4009 commit 260dc69

5 files changed

Lines changed: 75 additions & 83 deletions

File tree

lithoglyph/core-zig/build.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn build(b: *std.Build) void {
5656
});
5757

5858
// Unit tests for crypto
59-
const _crypto_tests = b.addTest(.{
59+
const crypto_tests = b.addTest(.{
6060
.name = "crypto-tests",
6161
.root_module = b.createModule(.{
6262
.root_source_file = b.path("src/crypto_test.zig"),
@@ -66,8 +66,10 @@ pub fn build(b: *std.Build) void {
6666
});
6767

6868
const run_blocks_tests = b.addRunArtifact(blocks_tests);
69+
const run_crypto_tests = b.addRunArtifact(crypto_tests);
6970

7071
const test_step = b.step("test", "Run unit tests");
7172
test_step.dependOn(&run_bridge_tests.step);
7273
test_step.dependOn(&run_blocks_tests.step);
74+
test_step.dependOn(&run_crypto_tests.step);
7375
}

lithoglyph/core-zig/src/blocks.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ pub const Block = struct {
247247

248248
/// Check if block payload is encrypted
249249
pub fn isEncrypted(self: *const Block) bool {
250-
return self.header.encrypted;
250+
return (self.header.flags & 0x02) != 0; // bit 1 = encrypted in BlockFlags
251251
}
252252

253253
/// Derive encryption key from master key

lithoglyph/core-zig/src/crypto.zig

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
//
44
// Provides AES-256-GCM encryption for block payloads
55
// Designed to integrate with Svalinn Vault's existing crypto system
6+
//
7+
// NOTE: All crypto functions below are PLACEHOLDERS for structure only.
8+
// Real implementation must use libsodium or std.crypto.aead.aes_gcm.
69

710
const std = @import("std");
811
const builtin = @import("builtin");
@@ -16,6 +19,9 @@ pub const AES256_KEY_SIZE: usize = 32; // 256 bits
1619
pub const AES_GCM_NONCE_SIZE: usize = 12; // 96 bits for GCM
1720
pub const AES_GCM_TAG_SIZE: usize = 16; // 128 bits authentication tag
1821

22+
// Encrypted flag bit in BlockHeader.flags
23+
const ENCRYPTED_FLAG: u32 = 0x02; // bit 1 of BlockFlags
24+
1925
// ============================================================
2026
// Error Types
2127
// ============================================================
@@ -38,15 +44,14 @@ pub fn encryptBlockPayload(
3844
block: *blocks.Block,
3945
key: [AES256_KEY_SIZE]u8,
4046
) !void {
41-
if (block.header.encrypted) {
47+
if ((block.header.flags & ENCRYPTED_FLAG) != 0) {
4248
return; // Already encrypted
4349
}
4450

45-
// Create nonce: block_id (8 bytes) + counter (4 bytes)
46-
var nonce = [AES_GCM_NONCE_SIZE]u8 = undefined;
47-
@memcpy(&nonce[0..8], &block.header.block_id, 8);
48-
@memcpy(&nonce[8..12], &block.header.sequence, 4);
49-
// Last 4 bytes zero for now
51+
// Create nonce: block_id (8 bytes) + lower 4 bytes of sequence
52+
var nonce: [AES_GCM_NONCE_SIZE]u8 = undefined;
53+
std.mem.writeInt(u64, nonce[0..8], block.header.block_id, .little);
54+
std.mem.writeInt(u32, nonce[8..12], @truncate(block.header.sequence), .little);
5055

5156
// Encrypt payload in-place
5257
const payload_len = block.header.payload_len;
@@ -59,20 +64,16 @@ pub fn encryptBlockPayload(
5964
return CryptoError.BufferTooSmall;
6065
}
6166

62-
// Encrypt using AES-GCM
63-
// Note: In real implementation, we'd use a proper crypto library
64-
// This is a placeholder showing the structure
6567
try aes256GcmEncryptInPlace(
66-
&block.payload[0..payload_len],
68+
block.payload[0..payload_len],
6769
&key,
6870
&nonce,
69-
&block.payload[payload_len..payload_len + AES_GCM_TAG_SIZE]
71+
block.payload[payload_len .. payload_len + AES_GCM_TAG_SIZE],
7072
);
7173

7274
// Update header
7375
block.header.payload_len = @intCast(payload_len + AES_GCM_TAG_SIZE);
74-
block.header.encrypted = true;
75-
block.header.flags |= @as(u32, @bitCast(@intFromEnum(blocks.BlockFlags.encrypted)));
76+
block.header.flags |= ENCRYPTED_FLAG;
7677

7778
// Recalculate checksum of encrypted data
7879
block.header.checksum = blocks.crc32c(&block.payload, block.header.payload_len);
@@ -83,7 +84,7 @@ pub fn decryptBlockPayload(
8384
block: *blocks.Block,
8485
key: [AES256_KEY_SIZE]u8,
8586
) !void {
86-
if (!block.header.encrypted) {
87+
if ((block.header.flags & ENCRYPTED_FLAG) == 0) {
8788
return; // Not encrypted
8889
}
8990

@@ -95,22 +96,20 @@ pub fn decryptBlockPayload(
9596
const payload_len = total_len - AES_GCM_TAG_SIZE;
9697

9798
// Create nonce (same as encryption)
98-
var nonce = [AES_GCM_NONCE_SIZE]u8 = undefined;
99-
@memcpy(&nonce[0..8], &block.header.block_id, 8);
100-
@memcpy(&nonce[8..12], &block.header.sequence, 4);
99+
var nonce: [AES_GCM_NONCE_SIZE]u8 = undefined;
100+
std.mem.writeInt(u64, nonce[0..8], block.header.block_id, .little);
101+
std.mem.writeInt(u32, nonce[8..12], @truncate(block.header.sequence), .little);
101102

102-
// Decrypt in-place
103103
try aes256GcmDecryptInPlace(
104-
&block.payload[0..payload_len],
104+
block.payload[0..payload_len],
105105
&key,
106106
&nonce,
107-
&block.payload[payload_len..total_len]
107+
block.payload[payload_len..total_len],
108108
);
109109

110110
// Update header
111111
block.header.payload_len = @intCast(payload_len);
112-
block.header.encrypted = false;
113-
block.header.flags &= ~@as(u32, @bitCast(@intFromEnum(blocks.BlockFlags.encrypted)));
112+
block.header.flags &= ~ENCRYPTED_FLAG;
114113

115114
// Recalculate checksum of decrypted data
116115
block.header.checksum = blocks.crc32c(&block.payload, block.header.payload_len);
@@ -121,105 +120,94 @@ pub fn decryptBlockPayload(
121120
// ============================================================
122121

123122
// Journal entries need special handling because they contain
124-
// both the operation and its inverse
123+
// both the operation and its inverse.
124+
// NOTE: caller owns `out_buf` and must ensure it is at least
125+
// `entry_data.len + AES_GCM_TAG_SIZE` bytes long.
125126
pub fn encryptJournalEntry(
126-
entry_data: []u8,
127+
entry_data: []const u8,
127128
key: [AES256_KEY_SIZE]u8,
128129
entry_id: u64,
129-
) ![]u8 {
130-
// Similar to block encryption but with different nonce
131-
var nonce = [AES_GCM_NONCE_SIZE]u8 = undefined;
132-
@memcpy(&nonce[0..8], &entry_id, 8);
133-
// Use fixed pattern for journal entries
130+
out_buf: []u8,
131+
) !usize {
132+
const needed = entry_data.len + AES_GCM_TAG_SIZE;
133+
if (out_buf.len < needed) return CryptoError.BufferTooSmall;
134+
135+
// Build nonce: entry_id (8 bytes) + "JNL\0" marker
136+
var nonce: [AES_GCM_NONCE_SIZE]u8 = undefined;
137+
std.mem.writeInt(u64, nonce[0..8], entry_id, .little);
134138
nonce[8] = 'J';
135139
nonce[9] = 'N';
136140
nonce[10] = 'L';
137141
nonce[11] = 0;
138142

139-
// Allocate buffer for encrypted data + tag
140-
var buffer: [entry_data.len + AES_GCM_TAG_SIZE]u8 = undefined;
141-
@memcpy(&buffer[0..entry_data.len], entry_data);
143+
@memcpy(out_buf[0..entry_data.len], entry_data);
142144

143-
// Encrypt
144145
try aes256GcmEncryptInPlace(
145-
&buffer[0..entry_data.len],
146+
out_buf[0..entry_data.len],
146147
&key,
147148
&nonce,
148-
&buffer[entry_data.len..entry_data.len + AES_GCM_TAG_SIZE]
149+
out_buf[entry_data.len..needed],
149150
);
150151

151-
return &buffer;
152+
return needed;
152153
}
153154

154155
// ============================================================
155156
// Key Derivation
156157
// ============================================================
157158

158-
// Derive encryption key from master key and block type
159+
// Derive encryption key from master key and block type.
160+
// PLACEHOLDER — real implementation must use HKDF or BLAKE3-KDF.
159161
pub fn deriveBlockKey(
160162
master_key: []const u8,
161163
block_type: blocks.BlockType,
162164
block_id: u64,
163165
) ![AES256_KEY_SIZE]u8 {
164-
// Use BLAKE3 for key derivation (matches vault's crypto)
165-
// In real implementation, use proper KDF
166-
var key = [AES256_KEY_SIZE]u8 = undefined;
167-
168-
// Simple XOR-based derivation for example
169-
// Real implementation would use HKDF or similar
166+
var key: [AES256_KEY_SIZE]u8 = undefined;
167+
const bt: u8 = @truncate(@intFromEnum(block_type));
170168
for (0..AES256_KEY_SIZE) |i| {
171-
if (i < master_key.len) {
172-
key[i] = master_key[i] ^ @as(u8, @truncate(block_type)) ^ @as(u8, @truncate(block_id >> (i % 8)));
173-
} else {
174-
key[i] = @as(u8, @truncate(block_type)) ^ @as(u8, @truncate(block_id >> (i % 8)));
175-
}
169+
const shift: u6 = @intCast(i % 8);
170+
const id_byte: u8 = @truncate(block_id >> shift);
171+
key[i] = if (i < master_key.len) master_key[i] ^ bt ^ id_byte else bt ^ id_byte;
176172
}
177-
178173
return key;
179174
}
180175

181176
// ============================================================
182-
// Placeholder Crypto Functions
183-
// (In real implementation, use libsodium or similar)
177+
// Placeholder Crypto Primitives
178+
// (Replace with libsodium / std.crypto.aead.aes_gcm in production)
184179
// ============================================================
185180

186181
fn aes256GcmEncryptInPlace(
187182
data: []u8,
188-
key: anytype,
189-
nonce: anytype,
183+
key: *const [AES256_KEY_SIZE]u8,
184+
nonce: *const [AES_GCM_NONCE_SIZE]u8,
190185
tag_out: []u8,
191186
) !void {
192-
// This is a placeholder - real implementation would use
193-
// a proper crypto library like libsodium or OpenSSL
194-
195-
// For now, just XOR with key (INSECURE - for structure only!)
196-
var key_bytes = @ptrCast([*]const u8, &key);
197-
for (0..data.len) |i| {
198-
data[i] ^= key_bytes[i % @intCast(key_bytes.len)];
187+
_ = nonce; // placeholder — nonce not used in XOR stub
188+
// INSECURE placeholder: XOR with key bytes only
189+
for (data, 0..) |*byte, i| {
190+
byte.* ^= key[i % AES256_KEY_SIZE];
199191
}
200-
201-
// Fill tag with dummy data
202-
for (0..tag_out.len) |i| {
203-
tag_out[i] = @as(u8, @truncate(i));
192+
// Fill tag with dummy pattern
193+
for (tag_out, 0..) |*b, i| {
194+
b.* = @truncate(i);
204195
}
205196
}
206197

207198
fn aes256GcmDecryptInPlace(
208199
data: []u8,
209-
key: anytype,
210-
nonce: anytype,
200+
key: *const [AES256_KEY_SIZE]u8,
201+
nonce: *const [AES_GCM_NONCE_SIZE]u8,
211202
tag: []const u8,
212203
) !void {
213-
// Verify tag (dummy check)
214-
for (0..tag.len) |i| {
215-
if (tag[i] != @as(u8, @truncate(i))) {
216-
return CryptoError.AuthenticationFailed;
217-
}
204+
_ = nonce; // placeholder
205+
// Verify dummy tag
206+
for (tag, 0..) |b, i| {
207+
if (b != @as(u8, @truncate(i))) return CryptoError.AuthenticationFailed;
218208
}
219-
220-
// Decrypt (same as encrypt for XOR)
221-
var key_bytes = @ptrCast([*]const u8, &key);
222-
for (0..data.len) |i| {
223-
data[i] ^= key_bytes[i % @intCast(key_bytes.len)];
209+
// INSECURE placeholder: XOR is its own inverse
210+
for (data, 0..) |*byte, i| {
211+
byte.* ^= key[i % AES256_KEY_SIZE];
224212
}
225213
}

lithoglyph/core-zig/src/crypto_test.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ test "double encryption detection" {
6464
const test_data = "test";
6565
try block.setPayload(test_data);
6666

67-
const key = [crypto.AES256_KEY_SIZE]u8{0};
67+
const key = std.mem.zeroes([crypto.AES256_KEY_SIZE]u8);
6868

6969
// First encryption
7070
try block.encrypt(key);

lithoglyph/gql-dt/bridge/lith_persist.zig

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,10 @@ pub fn lith_debug_init_status() i32 {
229229
// ============================================================================
230230

231231
test "basic persistence" {
232-
try std.testing.expectEqual(LithStatus.ok, lith_init(null, 0));
233-
try std.testing.expect(lith_is_init());
232+
// lith_init returns i32: 0 = already initialised, 42 = fresh init (special marker)
233+
const init_res = lith_init(null, 0);
234+
try std.testing.expect(init_res == 0 or init_res == 42);
235+
try std.testing.expect(lith_is_init(0));
234236

235237
var row_id: u64 = 0;
236238
try std.testing.expectEqual(LithStatus.ok, lith_insert_row(
@@ -250,7 +252,7 @@ test "basic persistence" {
250252
try std.testing.expectEqual(@as(u64, 1), row_id);
251253
try std.testing.expectEqual(@as(u64, 1), lith_table_count("test", 4));
252254

253-
try std.testing.expectEqual(LithStatus.ok, lith_save());
255+
try std.testing.expectEqual(LithStatus.ok, lith_save(0));
254256
try std.testing.expectEqual(LithStatus.ok, lith_close());
255-
try std.testing.expect(!lith_is_init());
257+
try std.testing.expect(!lith_is_init(0));
256258
}

0 commit comments

Comments
 (0)