tiny.sql.lattice
Defined in tiny.sql.
API (17)
Actions
Public operations.
EntryHasher.finishEntryHasher.initEntryHasher.updateState.addState.decodeState.digestState.encodeState.eqlState.isEmptyState.subtractentryState
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: lib/sql/src/lattice.zig
zig
const std = @import("std");const simd = @import("simd");const Aes128 = std.crypto.core.aes.Aes128;const Blake3 = std.crypto.hash.Blake3;const Sha256 = std.crypto.hash.sha2.Sha256;const native_endian = @import("builtin").cpu.arch.endian();pub const lane_count = 1024;pub const encoded_size = lane_count * @sizeOf(u16);pub const digest_size = Sha256.digest_length;const entry_tag = "sql.lattice.entry";const digest_tag = "sql.lattice";const Lanes = simd.ScalableTag(u16);/// Counter blocks the entry expansion builds and encrypts together.const group_blocks = 8;const block_size = 16;const group_size = group_blocks * block_size;const Words = @Vector(group_blocks, u64);comptime { std.debug.assert(lane_count % Lanes.lane_count == 0); std.debug.assert(encoded_size % group_size == 0);}pub const State = struct { lanes: [lane_count]u16, pub const empty = State{ .lanes = @splat(0) }; pub fn add(self: *State, entry: *const State) void { simd.transform1(Lanes, &self.lanes, &entry.lanes, WrappingAdd{}); } pub fn subtract(self: *State, entry: *const State) void { simd.transform1(Lanes, &self.lanes, &entry.lanes, WrappingSubtract{}); } pub fn isEmpty(self: *const State) bool { return simd.allEqual(Lanes, &self.lanes, 0); } pub fn eql(self: *const State, other: *const State) bool { return simd.equal(Lanes, &self.lanes, &other.lanes); } pub fn encode(self: *const State, out: *[encoded_size]u8) void { if (native_endian == .little) { @memcpy(out, std.mem.asBytes(&self.lanes)); } else { for (&self.lanes, 0..) |*lane, index| { std.mem.writeInt(u16, out[index * 2 ..][0..2], lane.*, .little); } } } pub fn decode(bytes: *const [encoded_size]u8) State { var state: State = undefined; @memcpy(std.mem.asBytes(&state.lanes), bytes); state.lanesFromLittle(); return state; } pub fn digest(self: *const State, entries: u64) [digest_size]u8 { var hasher = Sha256.init(.{}); hasher.update(digest_tag); var count: [8]u8 = undefined; std.mem.writeInt(u64, &count, entries, .big); hasher.update(&count); var encoded: [encoded_size]u8 = undefined; self.encode(&encoded); hasher.update(&encoded); var out: [digest_size]u8 = undefined; hasher.final(&out); return out; } /// Puts lanes read from little-endian bytes in native byte order. fn lanesFromLittle(self: *State) void { if (native_endian == .big) std.mem.byteSwapAllElements(u16, &self.lanes); }};const WrappingAdd = struct { pub fn call(_: WrappingAdd, comptime D: type, lanes: D.Vector, entry: D.Vector) D.Vector { return lanes +% entry; }};const WrappingSubtract = struct { pub fn call(_: WrappingSubtract, comptime D: type, lanes: D.Vector, entry: D.Vector) D.Vector { return lanes -% entry; }};pub const EntryHasher = struct { hasher: Blake3, pub fn init(entry_key: []const u8) EntryHasher { var hasher = Blake3.init(.{}); hasher.update(entry_tag); var length: [8]u8 = undefined; std.mem.writeInt(u64, &length, entry_key.len, .big); hasher.update(&length); hasher.update(entry_key); return .{ .hasher = hasher }; } pub fn update(self: *EntryHasher, value_chunk: []const u8) void { self.hasher.update(value_chunk); } pub fn finish(self: *const EntryHasher) State { var digest: [32]u8 = undefined; self.hasher.final(&digest); var state: State = undefined; expand(&digest, std.mem.asBytes(&state.lanes)); state.lanesFromLittle(); return state; }};pub fn entryState(entry_key: []const u8, value: []const u8) State { var hasher = EntryHasher.init(entry_key); hasher.update(value); return hasher.finish();}/// Writes the keystream that expands an entry digest into lattice lanes:/// AES-128 in counter mode over zeros, keyed by the digest's first half,/// counting from its second half read as a big-endian integer. Each group/// of counter blocks is written in place and encrypted there.fn expand(digest: *const [32]u8, out: *[encoded_size]u8) void { const aes = Aes128.initEnc(digest[0..16].*); var counter = std.mem.readInt(u128, digest[16..32], .big); var offset: usize = 0; while (offset < encoded_size) : (offset += group_size) { const group = out[offset..][0..group_size]; writeCounters(counter, group); aes.encryptWide(group_blocks, group, group); counter +%= group_blocks; }}/// Writes the big-endian counter blocks `first` through/// `first + group_blocks - 1`. Each vector lane adds its block's offset to/// the low word, and a lane whose low word wraps carries one into its high/// word, so no block formats a 128-bit integer a byte at a time.fn writeCounters(first: u128, out: *[group_size]u8) void { const low_first: u64 = @truncate(first); const high_first: u64 = @truncate(first >> 64); const low = @as(Words, @splat(low_first)) +% std.simd.iota(u64, group_blocks); const wrapped = low < @as(Words, @splat(low_first)); const carry = @select(u64, wrapped, @as(Words, @splat(1)), @as(Words, @splat(0))); const high = @as(Words, @splat(high_first)) +% carry; const words: [2 * group_blocks]u64 = @shuffle(u64, bigEndian(high), bigEndian(low), high_then_low); @memcpy(out, std.mem.asBytes(&words));}/// Shuffle mask that takes each block's high word and then its low word.const high_then_low = mask: { var lanes: [2 * group_blocks]i32 = undefined; for (0..group_blocks) |block| { lanes[2 * block] = @intCast(block); lanes[2 * block + 1] = ~@as(i32, @intCast(block)); } break :mask lanes;};fn bigEndian(words: Words) Words { return if (native_endian == .little) @byteSwap(words) else words;}/// Expands a digest with the standard library's counter mode, the/// construction `expand` reproduces.fn expandReference(digest: *const [32]u8, out: *[encoded_size]u8) void { const aes = Aes128.initEnc(digest[0..16].*); const zeros: [encoded_size]u8 = @splat(0); std.crypto.core.modes.ctr(@TypeOf(aes), aes, out, &zeros, digest[16..32].*, .big);}test "lattice expansion matches counter mode across counter carries" { var prng = std.Random.DefaultPrng.init(0x1a77_1ce5); const random = prng.random(); const low_ends = [_]u64{ 0, 1, std.math.maxInt(u64) - group_blocks, std.math.maxInt(u64) }; for (0..640) |round| { var digest: [32]u8 = undefined; random.bytes(&digest); if (round % 5 != 0) { const low_end = low_ends[round % low_ends.len]; const offset = random.uintAtMost(u64, 2 * group_blocks); std.mem.writeInt(u64, digest[24..32], low_end -% offset, .big); } if (round % 8 == 0) @memset(digest[16..24], 0xff); var fresh: [encoded_size]u8 = undefined; var expected: [encoded_size]u8 = undefined; expand(&digest, &fresh); expandReference(&digest, &expected); try std.testing.expectEqualSlices(u8, &expected, &fresh); }}test "lattice entry digests keep their persisted values" { const alpha = entryState("alpha", "one"); const empty = entryState("", ""); const beta = entryState("beta", "two"); var pair = State.empty; pair.add(&alpha); pair.add(&beta); const alpha_hex = "0fa6d7690ea57bfcb0e5c0fff010ad1d11a101560d69c9471d71dc2f87dbf558"; const empty_hex = "088cb4b17ab38962b95e7df2e7fe65dbaadab2ee20a7e48b21ce2c6210faaaad"; const pair_hex = "53e5f002f002639e7c61c98d23876fbd8ddd8afcd3132bb61b527b973370a9e1"; try expectDigest(alpha_hex, alpha.digest(1)); try expectDigest(empty_hex, empty.digest(1)); try expectDigest(pair_hex, pair.digest(2));}fn expectDigest(comptime hex: []const u8, actual: [digest_size]u8) !void { var expected: [digest_size]u8 = undefined; _ = try std.fmt.hexToBytes(&expected, hex); try std.testing.expectEqualSlices(u8, &expected, &actual);}test "lattice add and subtract invert" { const first = entryState("alpha", "one"); const second = entryState("beta", "two"); var state = State.empty; state.add(&first); state.add(&second); var expected = State.empty; expected.add(&first); state.subtract(&second); try std.testing.expect(state.eql(&expected)); state.subtract(&first); try std.testing.expect(state.isEmpty());}test "lattice entry framing separates key and value boundaries" { const joined_left = entryState("ab", "c"); const joined_right = entryState("a", "bc"); try std.testing.expect(!joined_left.eql(&joined_right)); const key_only = entryState("a", ""); const value_only = entryState("", "a"); try std.testing.expect(!key_only.eql(&value_only));}test "lattice streaming value chunks match one shot" { var hasher = EntryHasher.init("stream"); hasher.update("he"); hasher.update("l"); hasher.update("lo"); const streamed = hasher.finish(); const oneshot = entryState("stream", "hello"); try std.testing.expect(streamed.eql(&oneshot));}test "lattice encode decode round trips" { var state = State.empty; const entry = entryState("round", "trip"); state.add(&entry); var encoded: [encoded_size]u8 = undefined; state.encode(&encoded); const decoded = State.decode(&encoded); try std.testing.expect(state.eql(&decoded));}test "lattice digest binds entry count" { var state = State.empty; const entry = entryState("count", "bound"); state.add(&entry); const one = state.digest(1); const two = state.digest(2); try std.testing.expect(!std.mem.eql(u8, &one, &two));}test "lattice empty digest differs from populated digest" { var state = State.empty; const empty_digest = State.empty.digest(0); const entry = entryState("k", "v"); state.add(&entry); const populated = state.digest(1); try std.testing.expect(!std.mem.eql(u8, &empty_digest, &populated));}Source: lib/sql/src/root.zig:31
zig
pub const lattice = @import("lattice.zig");Complete caller list for lattice.entryState
13 direct callers.
lib.sql.src.lattice.test_lattice_add_and_subtract_invert[function] — test source atlib/sql/src/lattice.zig:228in nearest public ownertiny.sql.latticelib.sql.src.lattice.test_lattice_digest_binds_entry_count[function] — test source atlib/sql/src/lattice.zig:278in nearest public ownertiny.sql.latticelib.sql.src.lattice.test_lattice_empty_digest_differs_from_populated_digest[function] — test source atlib/sql/src/lattice.zig:288in nearest public ownertiny.sql.latticelib.sql.src.lattice.test_lattice_encode_decode_round_trips[function] — test source atlib/sql/src/lattice.zig:267in nearest public ownertiny.sql.latticelib.sql.src.lattice.test_lattice_entry_digests_keep_their_persisted_values[function] — test source atlib/sql/src/lattice.zig:207in nearest public ownertiny.sql.latticelib.sql.src.lattice.test_lattice_entry_framing_separates_key_and_value_boundaries[function] — test source atlib/sql/src/lattice.zig:246in nearest public ownertiny.sql.latticelib.sql.src.lattice.test_lattice_streaming_value_chunks_match_one_shot[function] — test source atlib/sql/src/lattice.zig:256in nearest public ownertiny.sql.latticelib.sql.src.properties.lattice.CodecProperty.property[function] — private source atlib/sql/src/properties/lattice.zig:158in nearest public ownerlib.sql.src.properties.latticelib.sql.src.properties.lattice.InverseProperty.property[function] — private source atlib/sql/src/properties/lattice.zig:67in nearest public ownerlib.sql.src.properties.latticelib.sql.src.properties.lattice.ModelProperty.property[function] — private source atlib/sql/src/properties/lattice.zig:97in nearest public ownerlib.sql.src.properties.latticelib.sql.src.properties.lattice.TreeProperty.property[function] — private source atlib/sql/src/properties/lattice.zig:184in nearest public ownerlib.sql.src.properties.latticelib.sql.src.tree.expectIdentityMatchesEntries[function] — private source atlib/sql/src/tree.zig:3511in nearest public ownertiny.sql.treetiny.sql.tree.rootFromSortedEntries[function] atlib/sql/src/tree.zig:261
Audit
| Definitions | 18 |
|---|---|
| Public names | 18 |
| Members | 2 |
| Version | 26.7.0 |
| Revision | daab053ee433 |