Skip to content

Commit d1ca00e

Browse files
authored
Merge pull request #38 from cipherstash/add-ste-vec-and-ore-cllw-changes
Add ORE CLLW and latest SteVec changes to EQL SQL files
2 parents 38a1133 + ca2a044 commit d1ca00e

File tree

5 files changed

+681
-7
lines changed

5 files changed

+681
-7
lines changed

justfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ build:
2727
#!/usr/bin/env bash
2828
set -euxo pipefail
2929

30-
cat sql/database-extensions/postgresql/install.sql sql/dsl-core.sql sql/dsl-config-schema.sql sql/dsl-config-functions.sql sql/dsl-encryptindex.sql > release/cipherstash-encrypt-dsl.sql
30+
cat sql/database-extensions/postgresql/install.sql sql/ore-cllw.sql sql/ste-vec.sql sql/dsl-core.sql sql/dsl-config-schema.sql sql/dsl-config-functions.sql sql/dsl-encryptindex.sql > release/cipherstash-encrypt-dsl.sql
3131

3232

3333
psql:

release/cipherstash-encrypt-dsl.sql

+340-3
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,341 @@ CREATE OPERATOR CLASS ore_64_8_v1_btree_ops DEFAULT FOR TYPE ore_64_8_v1 USING b
315315
OPERATOR 4 >=,
316316
OPERATOR 5 >,
317317
FUNCTION 1 compare_ore_64_8_v1(a ore_64_8_v1, b ore_64_8_v1);
318+
319+
---
320+
--- ORE CLLW types, functions, and operators
321+
---
322+
323+
-- Represents a ciphertext encrypted with the CLLW ORE scheme
324+
-- Each output block is 8-bits
325+
CREATE TYPE ore_cllw_8_v1 AS (
326+
bytes bytea
327+
);
328+
329+
330+
CREATE OR REPLACE FUNCTION __compare_inner_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1)
331+
RETURNS int AS $$
332+
DECLARE
333+
len_a INT;
334+
x BYTEA;
335+
y BYTEA;
336+
i INT;
337+
differing RECORD;
338+
BEGIN
339+
len_a := LENGTH(a.bytes);
340+
341+
-- Iterate over each byte and compare them
342+
FOR i IN 1..len_a LOOP
343+
x := SUBSTRING(a.bytes FROM i FOR 1);
344+
y := SUBSTRING(b.bytes FROM i FOR 1);
345+
346+
-- Check if there's a difference
347+
IF x != y THEN
348+
differing := (x, y);
349+
EXIT;
350+
END IF;
351+
END LOOP;
352+
353+
-- If a difference is found, compare the bytes as in Rust logic
354+
IF differing IS NOT NULL THEN
355+
IF (get_byte(y, 0) + 1) % 256 = get_byte(x, 0) THEN
356+
RETURN 1;
357+
ELSE
358+
RETURN -1;
359+
END IF;
360+
ELSE
361+
RETURN 0;
362+
END IF;
363+
END;
364+
$$ LANGUAGE plpgsql;
365+
366+
367+
CREATE OR REPLACE FUNCTION compare_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1)
368+
RETURNS int AS $$
369+
DECLARE
370+
len_a INT;
371+
len_b INT;
372+
x BYTEA;
373+
y BYTEA;
374+
i INT;
375+
differing RECORD;
376+
BEGIN
377+
-- Check if the lengths of the two bytea arguments are the same
378+
len_a := LENGTH(a.bytes);
379+
len_b := LENGTH(b.bytes);
380+
381+
IF len_a != len_b THEN
382+
RAISE EXCEPTION 'Bytea arguments must have the same length';
383+
END IF;
384+
385+
RETURN __compare_inner_ore_cllw_8_v1(a, b);
386+
END;
387+
$$ LANGUAGE plpgsql;
388+
389+
CREATE OR REPLACE FUNCTION compare_lex_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1)
390+
RETURNS int AS $$
391+
DECLARE
392+
len_a INT;
393+
len_b INT;
394+
cmp_result int;
395+
BEGIN
396+
-- Get the lengths of both bytea inputs
397+
len_a := LENGTH(a.bytes);
398+
len_b := LENGTH(b.bytes);
399+
400+
-- Handle empty cases
401+
IF len_a = 0 AND len_b = 0 THEN
402+
RETURN 0;
403+
ELSIF len_a = 0 THEN
404+
RETURN -1;
405+
ELSIF len_b = 0 THEN
406+
RETURN 1;
407+
END IF;
408+
409+
-- Use the compare_bytea function to compare byte by byte
410+
cmp_result := __compare_inner_ore_cllw_8_v1(a, b);
411+
412+
-- If the comparison returns 'less' or 'greater', return that result
413+
IF cmp_result = -1 THEN
414+
RETURN -1;
415+
ELSIF cmp_result = 1 THEN
416+
RETURN 1;
417+
END IF;
418+
419+
-- If the bytea comparison is 'equal', compare lengths
420+
IF len_a < len_b THEN
421+
RETURN 1;
422+
ELSIF len_a > len_b THEN
423+
RETURN -1;
424+
ELSE
425+
RETURN 0;
426+
END IF;
427+
END;
428+
$$ LANGUAGE plpgsql;
429+
430+
CREATE OR REPLACE FUNCTION ore_cllw_8_v1_eq(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$
431+
SELECT compare_ore_cllw_8_v1(a, b) = 0
432+
$$ LANGUAGE SQL;
433+
434+
CREATE OR REPLACE FUNCTION ore_cllw_8_v1_neq(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$
435+
SELECT compare_ore_cllw_8_v1(a, b) <> 0
436+
$$ LANGUAGE SQL;
437+
438+
CREATE OR REPLACE FUNCTION ore_cllw_8_v1_lt(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$
439+
SELECT compare_ore_cllw_8_v1(a, b) = -1
440+
$$ LANGUAGE SQL;
441+
442+
CREATE OR REPLACE FUNCTION ore_cllw_8_v1_lte(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$
443+
SELECT compare_ore_cllw_8_v1(a, b) != 1
444+
$$ LANGUAGE SQL;
445+
446+
CREATE OR REPLACE FUNCTION ore_cllw_8_v1_gt(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$
447+
SELECT compare_ore_cllw_8_v1(a, b) = 1
448+
$$ LANGUAGE SQL;
449+
450+
CREATE OR REPLACE FUNCTION ore_cllw_8_v1_gte(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$
451+
SELECT compare_ore_cllw_8_v1(a, b) != -1
452+
$$ LANGUAGE SQL;
453+
454+
CREATE OPERATOR = (
455+
PROCEDURE="ore_cllw_8_v1_eq",
456+
LEFTARG=ore_cllw_8_v1,
457+
RIGHTARG=ore_cllw_8_v1,
458+
NEGATOR = <>,
459+
RESTRICT = eqsel,
460+
JOIN = eqjoinsel,
461+
HASHES,
462+
MERGES
463+
);
464+
465+
CREATE OPERATOR <> (
466+
PROCEDURE="ore_cllw_8_v1_neq",
467+
LEFTARG=ore_cllw_8_v1,
468+
RIGHTARG=ore_cllw_8_v1,
469+
NEGATOR = =,
470+
RESTRICT = eqsel,
471+
JOIN = eqjoinsel,
472+
HASHES,
473+
MERGES
474+
);
475+
476+
CREATE OPERATOR > (
477+
PROCEDURE="ore_cllw_8_v1_gt",
478+
LEFTARG=ore_cllw_8_v1,
479+
RIGHTARG=ore_cllw_8_v1,
480+
NEGATOR = <=,
481+
RESTRICT = scalarltsel,
482+
JOIN = scalarltjoinsel,
483+
HASHES,
484+
MERGES
485+
);
486+
487+
CREATE OPERATOR < (
488+
PROCEDURE="ore_cllw_8_v1_lt",
489+
LEFTARG=ore_cllw_8_v1,
490+
RIGHTARG=ore_cllw_8_v1,
491+
NEGATOR = >=,
492+
RESTRICT = scalargtsel,
493+
JOIN = scalargtjoinsel,
494+
HASHES,
495+
MERGES
496+
);
497+
498+
CREATE OPERATOR >= (
499+
PROCEDURE="ore_cllw_8_v1_gte",
500+
LEFTARG=ore_cllw_8_v1,
501+
RIGHTARG=ore_cllw_8_v1,
502+
NEGATOR = <,
503+
RESTRICT = scalarltsel,
504+
JOIN = scalarltjoinsel,
505+
HASHES,
506+
MERGES
507+
);
508+
509+
CREATE OPERATOR <= (
510+
PROCEDURE="ore_cllw_8_v1_lte",
511+
LEFTARG=ore_cllw_8_v1,
512+
RIGHTARG=ore_cllw_8_v1,
513+
NEGATOR = >,
514+
RESTRICT = scalargtsel,
515+
JOIN = scalargtjoinsel,
516+
HASHES,
517+
MERGES
518+
);
519+
520+
CREATE OPERATOR FAMILY ore_cllw_8_v1_btree_ops USING btree;
521+
CREATE OPERATOR CLASS ore_cllw_8_v1_btree_ops DEFAULT FOR TYPE ore_cllw_8_v1 USING btree FAMILY ore_cllw_8_v1_btree_ops AS
522+
OPERATOR 1 <,
523+
OPERATOR 2 <=,
524+
OPERATOR 3 =,
525+
OPERATOR 4 >=,
526+
OPERATOR 5 >,
527+
FUNCTION 1 compare_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1);
528+
529+
---
530+
--- SteVec types, functions, and operators
531+
---
532+
533+
CREATE TYPE ste_vec_v1_entry AS (
534+
tokenized_selector text,
535+
term ore_cllw_8_v1,
536+
ciphertext text
537+
);
538+
539+
CREATE TYPE cs_ste_vec_index_v1 AS (
540+
entries ste_vec_v1_entry[]
541+
);
542+
543+
-- Determine if a == b (ignoring ciphertext values)
544+
CREATE OR REPLACE FUNCTION ste_vec_v1_entry_eq(a ste_vec_v1_entry, b ste_vec_v1_entry)
545+
RETURNS boolean AS $$
546+
DECLARE
547+
sel_cmp int;
548+
term_cmp int;
549+
BEGIN
550+
-- Constant time comparison
551+
IF a.tokenized_selector = b.tokenized_selector THEN
552+
sel_cmp := 1;
553+
ELSE
554+
sel_cmp := 0;
555+
END IF;
556+
IF a.term = b.term THEN
557+
term_cmp := 1;
558+
ELSE
559+
term_cmp := 0;
560+
END IF;
561+
RETURN (sel_cmp # term_cmp) = 0;
562+
END;
563+
$$ LANGUAGE plpgsql;
564+
565+
-- Determine if a contains b (ignoring ciphertext values)
566+
CREATE OR REPLACE FUNCTION ste_vec_v1_logical_contains(a cs_ste_vec_index_v1, b cs_ste_vec_index_v1)
567+
RETURNS boolean AS $$
568+
DECLARE
569+
result boolean;
570+
intermediate_result boolean;
571+
BEGIN
572+
result := true;
573+
IF array_length(b.entries, 1) IS NULL THEN
574+
RETURN result;
575+
END IF;
576+
FOR i IN 1..array_length(b.entries, 1) LOOP
577+
intermediate_result := ste_vec_v1_entry_array_contains_entry(a.entries, b.entries[i]);
578+
result := result AND intermediate_result;
579+
END LOOP;
580+
RETURN result;
581+
END;
582+
$$ LANGUAGE plpgsql;
583+
584+
-- Determine if a contains b (ignoring ciphertext values)
585+
CREATE OR REPLACE FUNCTION ste_vec_v1_entry_array_contains_entry(a ste_vec_v1_entry[], b ste_vec_v1_entry)
586+
RETURNS boolean AS $$
587+
DECLARE
588+
result boolean;
589+
intermediate_result boolean;
590+
BEGIN
591+
IF array_length(a, 1) IS NULL THEN
592+
RETURN false;
593+
END IF;
594+
595+
result := false;
596+
FOR i IN 1..array_length(a, 1) LOOP
597+
intermediate_result := a[i].tokenized_selector = b.tokenized_selector AND a[i].term = b.term;
598+
result := result OR intermediate_result;
599+
END LOOP;
600+
RETURN result;
601+
END;
602+
$$ LANGUAGE plpgsql;
603+
604+
-- Determine if a is contained by b (ignoring ciphertext values)
605+
CREATE OR REPLACE FUNCTION ste_vec_v1_logical_is_contained(a cs_ste_vec_index_v1, b cs_ste_vec_index_v1)
606+
RETURNS boolean AS $$
607+
BEGIN
608+
RETURN ste_vec_v1_logical_contains(b, a);
609+
END;
610+
$$ LANGUAGE plpgsql;
611+
612+
613+
CREATE OR REPLACE FUNCTION jsonb_to_cs_ste_vec_index_v1(input jsonb)
614+
RETURNS cs_ste_vec_index_v1 AS $$
615+
DECLARE
616+
vec_entry ste_vec_v1_entry;
617+
entry_array ste_vec_v1_entry[];
618+
entry_json jsonb;
619+
entry_json_array jsonb[];
620+
entry_array_length int;
621+
i int;
622+
BEGIN
623+
FOR entry_json IN SELECT * FROM jsonb_array_elements(input)
624+
LOOP
625+
vec_entry := ROW(
626+
entry_json->>0,
627+
ROW((entry_json->>1)::bytea)::ore_cllw_8_v1,
628+
entry_json->>2
629+
)::ste_vec_v1_entry;
630+
entry_array := array_append(entry_array, vec_entry);
631+
END LOOP;
632+
633+
RETURN ROW(entry_array)::cs_ste_vec_index_v1;
634+
END;
635+
$$ LANGUAGE plpgsql;
636+
637+
CREATE CAST (jsonb AS cs_ste_vec_index_v1)
638+
WITH FUNCTION jsonb_to_cs_ste_vec_index_v1(jsonb) AS IMPLICIT;
639+
640+
CREATE OPERATOR @> (
641+
PROCEDURE="ste_vec_v1_logical_contains",
642+
LEFTARG=cs_ste_vec_index_v1,
643+
RIGHTARG=cs_ste_vec_index_v1,
644+
COMMUTATOR = <@
645+
);
646+
647+
CREATE OPERATOR <@ (
648+
PROCEDURE="ste_vec_v1_logical_is_contained",
649+
LEFTARG=cs_ste_vec_index_v1,
650+
RIGHTARG=cs_ste_vec_index_v1,
651+
COMMUTATOR = @>
652+
);
318653
DROP CAST IF EXISTS (text AS ore_64_8_v1_term);
319654

320655
DROP FUNCTION IF EXISTS cs_match_v1;
@@ -338,7 +673,6 @@ DROP DOMAIN IF EXISTS cs_unique_index_v1;
338673

339674
CREATE DOMAIN cs_match_index_v1 AS smallint[];
340675
CREATE DOMAIN cs_unique_index_v1 AS text;
341-
CREATE DOMAIN cs_ste_vec_index_v1 AS text[];
342676

343677
-- cs_encrypted_v1 is a column type and cannot be dropped if in use
344678
DO $$
@@ -354,7 +688,10 @@ CREATE FUNCTION _cs_encrypted_check_kind(val jsonb)
354688
RETURNS BOOLEAN
355689
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
356690
BEGIN ATOMIC
357-
RETURN (val->>'k' = 'ct' AND val ? 'c') AND NOT val ? 'p';
691+
RETURN (
692+
(val->>'k' = 'ct' AND val ? 'c') OR
693+
(val->>'k' = 'sv' AND val ? 'sv')
694+
) AND NOT val ? 'p';
358695
END;
359696

360697
CREATE FUNCTION cs_check_encrypted_v1(val jsonb)
@@ -451,7 +788,7 @@ CREATE OR REPLACE FUNCTION cs_ste_vec_v1_v0_0(col jsonb)
451788
RETURNS cs_ste_vec_index_v1
452789
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
453790
BEGIN ATOMIC
454-
SELECT ARRAY(SELECT jsonb_array_elements(col->'sv'))::cs_ste_vec_index_v1;
791+
SELECT (col->'sv')::cs_ste_vec_index_v1;
455792
END;
456793

457794
CREATE OR REPLACE FUNCTION cs_ste_vec_v1_v0(col jsonb)

0 commit comments

Comments
 (0)