From df12fc9fa597d2bee8f69ded968afaa0cc6f4843 Mon Sep 17 00:00:00 2001 From: Drew Thomas Date: Tue, 29 Oct 2024 18:25:41 +1100 Subject: [PATCH 1/4] Update 001-ore-cllw.sql with variable length ORE fixes --- sql/001-ore-cllw.sql | 203 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 188 insertions(+), 15 deletions(-) diff --git a/sql/001-ore-cllw.sql b/sql/001-ore-cllw.sql index 233dda0..c5e5cb3 100644 --- a/sql/001-ore-cllw.sql +++ b/sql/001-ore-cllw.sql @@ -3,16 +3,46 @@ --- ORE CLLW types, functions, and operators --- --- Represents a ciphertext encrypted with the CLLW ORE scheme +-- Represents a ciphertext encrypted with the CLLW ORE scheme for a fixed output size -- Each output block is 8-bits CREATE TYPE ore_cllw_8_v1 AS ( bytes bytea ); +-- Represents a ciphertext encrypted with the CLLW ORE scheme for a variable output size +-- Each output block is 8-bits +CREATE TYPE ore_cllw_8_variable_v1 AS ( + bytes bytea +); + +DROP FUNCTION IF EXISTS __bytea_ct_eq(a bytea, b bytea); + +-- Constant time comparison of 2 bytea values +CREATE FUNCTION __bytea_ct_eq(a bytea, b bytea) RETURNS boolean AS $$ +DECLARE + result boolean; + differing bytea; +BEGIN + -- Check if the bytea values are the same length + IF LENGTH(a) != LENGTH(b) THEN + RETURN false; + END IF; + + -- Compare each byte in the bytea values + result := true; + FOR i IN 1..LENGTH(a) LOOP + IF SUBSTRING(a FROM i FOR 1) != SUBSTRING(b FROM i FOR 1) THEN + result := result AND false; + END IF; + END LOOP; + + RETURN result; +END; +$$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS __compare_inner_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1); +DROP FUNCTION IF EXISTS __compare_inner_ore_cllw_8_v1(a bytea, b bytea); -CREATE FUNCTION __compare_inner_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1) +CREATE FUNCTION __compare_inner_ore_cllw_8_v1(a bytea, b bytea) RETURNS int AS $$ DECLARE len_a INT; @@ -21,12 +51,12 @@ DECLARE i INT; differing RECORD; BEGIN - len_a := LENGTH(a.bytes); + len_a := LENGTH(a); -- Iterate over each byte and compare them FOR i IN 1..len_a LOOP - x := SUBSTRING(a.bytes FROM i FOR 1); - y := SUBSTRING(b.bytes FROM i FOR 1); + x := SUBSTRING(a FROM i FOR 1); + y := SUBSTRING(b FROM i FOR 1); -- Check if there's a difference IF x != y THEN @@ -66,22 +96,24 @@ BEGIN len_b := LENGTH(b.bytes); IF len_a != len_b THEN - RAISE EXCEPTION 'Bytea arguments must have the same length'; + RAISE EXCEPTION 'Numeric ORE comparison requires bytea values of the same length'; END IF; - RETURN __compare_inner_ore_cllw_8_v1(a, b); + RETURN __compare_inner_ore_cllw_8_v1(a.bytes, b.bytes); END; $$ LANGUAGE plpgsql; -DROP FUNCTION IF EXISTS compare_lex_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1); +DROP FUNCTION IF EXISTS compare_lex_ore_cllw_8_v1(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); -CREATE FUNCTION compare_lex_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1) +CREATE FUNCTION compare_lex_ore_cllw_8_v1(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS int AS $$ DECLARE len_a INT; len_b INT; - cmp_result int; + -- length of the common part of the two bytea values + common_len INT; + cmp_result INT; BEGIN -- Get the lengths of both bytea inputs len_a := LENGTH(a.bytes); @@ -96,8 +128,18 @@ BEGIN RETURN 1; END IF; + -- Find the length of the shorter bytea + IF len_a < len_b THEN + common_len := len_a; + ELSE + common_len := len_b; + END IF; + -- Use the compare_bytea function to compare byte by byte - cmp_result := __compare_inner_ore_cllw_8_v1(a, b); + cmp_result := __compare_inner_ore_cllw_8_v1( + SUBSTRING(a.bytes FROM 1 FOR common_len), + SUBSTRING(b.bytes FROM 1 FOR common_len) + ); -- If the comparison returns 'less' or 'greater', return that result IF cmp_result = -1 THEN @@ -122,7 +164,7 @@ DROP FUNCTION IF EXISTS ore_cllw_8_v1_eq(a ore_cllw_8_v1, b ore_cllw_8_v1); CREATE FUNCTION ore_cllw_8_v1_eq(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$ - SELECT compare_ore_cllw_8_v1(a, b) = 0 + SELECT __bytea_ct_eq(a.bytes, b.bytes) $$ LANGUAGE SQL; @@ -130,7 +172,7 @@ DROP FUNCTION IF EXISTS ore_cllw_8_v1_neq(a ore_cllw_8_v1, b ore_cllw_8_v1); CREATE FUNCTION ore_cllw_8_v1_neq(a ore_cllw_8_v1, b ore_cllw_8_v1) RETURNS boolean AS $$ - SELECT compare_ore_cllw_8_v1(a, b) <> 0 + SELECT not __bytea_ct_eq(a.bytes, b.bytes) $$ LANGUAGE SQL; @@ -249,12 +291,127 @@ CREATE OPERATOR <= ( MERGES ); +-- Lexical comparison operators + +DROP FUNCTION IF EXISTS ore_cllw_8_variable_v1_eq(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); + +CREATE OR REPLACE FUNCTION ore_cllw_8_variable_v1_eq(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS boolean AS $$ + SELECT __bytea_ct_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS ore_cllw_8_variable_v1_neq(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); + +CREATE OR REPLACE FUNCTION ore_cllw_8_variable_v1_neq(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS boolean AS $$ + SELECT not __bytea_ct_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS ore_cllw_8_v1_lt_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); + +CREATE OR REPLACE FUNCTION ore_cllw_8_v1_lt_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS boolean AS $$ + SELECT compare_lex_ore_cllw_8_v1(a, b) = -1 +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS ore_cllw_8_v1_lte_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); + +CREATE OR REPLACE FUNCTION ore_cllw_8_v1_lte_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS boolean AS $$ + SELECT compare_lex_ore_cllw_8_v1(a, b) != 1 +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS ore_cllw_8_v1_gt_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); + +CREATE OR REPLACE FUNCTION ore_cllw_8_v1_gt_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS boolean AS $$ + SELECT compare_lex_ore_cllw_8_v1(a, b) = 1 +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS ore_cllw_8_v1_gte_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); + +CREATE OR REPLACE FUNCTION ore_cllw_8_v1_gte_lex(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1) RETURNS boolean AS $$ + SELECT compare_lex_ore_cllw_8_v1(a, b) != -1 +$$ LANGUAGE SQL; + +DROP OPERATOR IF EXISTS = (ore_cllw_8_variable_v1, ore_cllw_8_variable_v1); + +CREATE OPERATOR = ( + PROCEDURE="ore_cllw_8_variable_v1_eq", + LEFTARG=ore_cllw_8_variable_v1, + RIGHTARG=ore_cllw_8_variable_v1, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS <> (ore_cllw_8_variable_v1, ore_cllw_8_variable_v1); + +CREATE OPERATOR <> ( + PROCEDURE="ore_cllw_8_variable_v1_neq", + LEFTARG=ore_cllw_8_variable_v1, + RIGHTARG=ore_cllw_8_variable_v1, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS > (ore_cllw_8_variable_v1, ore_cllw_8_variable_v1); + +CREATE OPERATOR > ( + PROCEDURE="ore_cllw_8_v1_gt_lex", + LEFTARG=ore_cllw_8_variable_v1, + RIGHTARG=ore_cllw_8_variable_v1, + NEGATOR = <=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS < (ore_cllw_8_variable_v1, ore_cllw_8_variable_v1); + +CREATE OPERATOR < ( + PROCEDURE="ore_cllw_8_v1_lt_lex", + LEFTARG=ore_cllw_8_variable_v1, + RIGHTARG=ore_cllw_8_variable_v1, + NEGATOR = >=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS >= (ore_cllw_8_variable_v1, ore_cllw_8_variable_v1); + +CREATE OPERATOR >= ( + PROCEDURE="ore_cllw_8_v1_gte_lex", + LEFTARG=ore_cllw_8_variable_v1, + RIGHTARG=ore_cllw_8_variable_v1, + NEGATOR = <, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS <= (ore_cllw_8_variable_v1, ore_cllw_8_variable_v1); + +CREATE OPERATOR <= ( + PROCEDURE="ore_cllw_8_v1_lte_lex", + LEFTARG=ore_cllw_8_variable_v1, + RIGHTARG=ore_cllw_8_variable_v1, + NEGATOR = >, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + DROP OPERATOR FAMILY IF EXISTS ore_cllw_8_v1_btree_ops USING btree; CREATE OPERATOR FAMILY ore_cllw_8_v1_btree_ops USING btree; - DROP OPERATOR CLASS IF EXISTS ore_cllw_8_v1_btree_ops USING btree; 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 @@ -264,3 +421,19 @@ CREATE OPERATOR CLASS ore_cllw_8_v1_btree_ops DEFAULT FOR TYPE ore_cllw_8_v1 USI OPERATOR 4 >=, OPERATOR 5 >, FUNCTION 1 compare_ore_cllw_8_v1(a ore_cllw_8_v1, b ore_cllw_8_v1); + +-- Lexical comparison operator class + +DROP OPERATOR FAMILY IF EXISTS ore_cllw_8_v1_variable_btree_ops USING btree; + +CREATE OPERATOR FAMILY ore_cllw_8_v1_variable_btree_ops USING btree; + +DROP OPERATOR CLASS IF EXISTS ore_cllw_8_v1_variable_btree_ops USING btree; + +CREATE OPERATOR CLASS ore_cllw_8_v1_variable_btree_ops DEFAULT FOR TYPE ore_cllw_8_variable_v1 USING btree FAMILY ore_cllw_8_v1_variable_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 compare_lex_ore_cllw_8_v1(a ore_cllw_8_variable_v1, b ore_cllw_8_variable_v1); From e274bb14c3f787bdbab75220d28dd6c466c4d0ba Mon Sep 17 00:00:00 2001 From: Drew Thomas Date: Tue, 29 Oct 2024 18:28:01 +1100 Subject: [PATCH 2/4] Add cs_ste_vec_value_v1, cs_ste_vec_term_v1, and cs_ste_vec_terms_v1 to 002-ste-vec.sql Also add in the SteVec side of variable length ORE fixes. --- sql/002-ste-vec.sql | 308 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 296 insertions(+), 12 deletions(-) diff --git a/sql/002-ste-vec.sql b/sql/002-ste-vec.sql index d2f2bf1..49f8bb8 100644 --- a/sql/002-ste-vec.sql +++ b/sql/002-ste-vec.sql @@ -2,20 +2,304 @@ --- --- SteVec types, functions, and operators --- -CREATE TYPE ste_vec_v1_entry AS ( + +CREATE TYPE cs_ste_vec_encrypted_term_v1 AS ( + bytes bytea +); + +DROP FUNCTION IF EXISTS compare_ste_vec_encrypted_term_v1(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION compare_ste_vec_encrypted_term_v1(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) +RETURNS INT AS $$ +DECLARE + header_a INT; + header_b INT; + body_a BYTEA; + body_b BYTEA; +BEGIN + -- `get_byte` is 0-indexed + header_a := get_byte(a.bytes, 0); + header_b := get_byte(b.bytes, 0); + + IF header_a != header_b THEN + RAISE EXCEPTION 'compare_ste_vec_encrypted_term_v1: expected equal header bytes'; + END IF; + + -- `substr` is 1-indexed (yes, `subtr` starts at 1 and `get_byte` starts at 0). + body_a := substr(a.bytes, 2); + body_b := substr(b.bytes, 2); + + CASE header_a + WHEN 0 THEN + RAISE EXCEPTION 'compare_ste_vec_encrypted_term_v1: can not compare MAC terms'; + WHEN 1 THEN + RETURN compare_ore_cllw_8_v1(ROW(body_a)::ore_cllw_8_v1, ROW(body_b)::ore_cllw_8_v1); + WHEN 2 THEN + RETURN compare_lex_ore_cllw_8_v1(ROW(body_a)::ore_cllw_8_variable_v1, ROW(body_b)::ore_cllw_8_variable_v1); + ELSE + RAISE EXCEPTION 'compare_ste_vec_encrypted_term_v1: invalid header for cs_ste_vec_encrypted_term_v1: header "%", body "%', header_a, body_a; + END CASE; +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS cs_ste_vec_encrypted_term_eq(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION cs_ste_vec_encrypted_term_eq(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) RETURNS boolean AS $$ + SELECT __bytea_ct_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS cs_ste_vec_encrypted_term_neq(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION cs_ste_vec_encrypted_term_neq(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) RETURNS boolean AS $$ + SELECT not __bytea_ct_eq(a.bytes, b.bytes) +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS cs_ste_vec_encrypted_term_lt(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION cs_ste_vec_encrypted_term_lt(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) RETURNS boolean AS $$ + SELECT compare_ste_vec_encrypted_term_v1(a, b) = -1 +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS cs_ste_vec_encrypted_term_lte(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION cs_ste_vec_encrypted_term_lte(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) RETURNS boolean AS $$ + SELECT compare_ste_vec_encrypted_term_v1(a, b) != 1 +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS cs_ste_vec_encrypted_term_gt(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION cs_ste_vec_encrypted_term_gt(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) RETURNS boolean AS $$ + SELECT compare_ste_vec_encrypted_term_v1(a, b) = 1 +$$ LANGUAGE SQL; + +DROP FUNCTION IF EXISTS cs_ste_vec_encrypted_term_gte(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE FUNCTION cs_ste_vec_encrypted_term_gte(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1) RETURNS boolean AS $$ + SELECT compare_ste_vec_encrypted_term_v1(a, b) != -1 +$$ LANGUAGE SQL; + +DROP OPERATOR IF EXISTS = (cs_ste_vec_encrypted_term_v1, cs_ste_vec_encrypted_term_v1); + +CREATE OPERATOR = ( + PROCEDURE="cs_ste_vec_encrypted_term_eq", + LEFTARG=cs_ste_vec_encrypted_term_v1, + RIGHTARG=cs_ste_vec_encrypted_term_v1, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS <> (cs_ste_vec_encrypted_term_v1, cs_ste_vec_encrypted_term_v1); + +CREATE OPERATOR <> ( + PROCEDURE="cs_ste_vec_encrypted_term_neq", + LEFTARG=cs_ste_vec_encrypted_term_v1, + RIGHTARG=cs_ste_vec_encrypted_term_v1, + NEGATOR = =, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS > (cs_ste_vec_encrypted_term_v1, cs_ste_vec_encrypted_term_v1); + +CREATE OPERATOR > ( + PROCEDURE="cs_ste_vec_encrypted_term_gt", + LEFTARG=cs_ste_vec_encrypted_term_v1, + RIGHTARG=cs_ste_vec_encrypted_term_v1, + NEGATOR = <=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS < (cs_ste_vec_encrypted_term_v1, cs_ste_vec_encrypted_term_v1); + +CREATE OPERATOR < ( + PROCEDURE="cs_ste_vec_encrypted_term_lt", + LEFTARG=cs_ste_vec_encrypted_term_v1, + RIGHTARG=cs_ste_vec_encrypted_term_v1, + NEGATOR = >=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS >= (cs_ste_vec_encrypted_term_v1, cs_ste_vec_encrypted_term_v1); + +CREATE OPERATOR >= ( + PROCEDURE="cs_ste_vec_encrypted_term_gte", + LEFTARG=cs_ste_vec_encrypted_term_v1, + RIGHTARG=cs_ste_vec_encrypted_term_v1, + NEGATOR = <, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR IF EXISTS <= (cs_ste_vec_encrypted_term_v1, cs_ste_vec_encrypted_term_v1); + +CREATE OPERATOR <= ( + PROCEDURE="cs_ste_vec_encrypted_term_lte", + LEFTARG=cs_ste_vec_encrypted_term_v1, + RIGHTARG=cs_ste_vec_encrypted_term_v1, + NEGATOR = >, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel, + HASHES, + MERGES +); + +DROP OPERATOR FAMILY IF EXISTS cs_ste_vec_encrypted_term_v1_btree_ops USING btree; + +CREATE OPERATOR FAMILY cs_ste_vec_encrypted_term_v1_btree_ops USING btree; + +DROP OPERATOR CLASS IF EXISTS cs_ste_vec_encrypted_term_v1_btree_ops USING btree; + +CREATE OPERATOR CLASS cs_ste_vec_encrypted_term_v1_btree_ops DEFAULT FOR TYPE cs_ste_vec_encrypted_term_v1 USING btree FAMILY cs_ste_vec_encrypted_term_v1_btree_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 compare_ste_vec_encrypted_term_v1(a cs_ste_vec_encrypted_term_v1, b cs_ste_vec_encrypted_term_v1); + +CREATE TYPE cs_ste_vec_v1_entry AS ( tokenized_selector text, - term ore_cllw_8_v1, + term cs_ste_vec_encrypted_term_v1, ciphertext text ); CREATE TYPE cs_ste_vec_index_v1 AS ( - entries ste_vec_v1_entry[] + entries cs_ste_vec_v1_entry[] ); +DROP FUNCTION IF EXISTS cs_ste_vec_value_v1(col jsonb, selector jsonb); + +-- col: already encrypted payload +-- selector: already encrypted payload +-- returns a value in the format of our custom jsonb schema that will be decrypted +CREATE FUNCTION cs_ste_vec_value_v1(col jsonb, selector jsonb) +RETURNS jsonb AS $$ +DECLARE + ste_vec_index cs_ste_vec_index_v1; + target_selector text; + found text; + ignored text; + i integer; +BEGIN + ste_vec_index := cs_ste_vec_v1(col); + target_selector := selector->>'svs'; + + FOR i IN 1..array_length(ste_vec_index.entries, 1) LOOP + -- The ELSE part is to help ensure constant time operation. + -- The result is thrown away. + IF ste_vec_index.entries[i].tokenized_selector = target_selector THEN + found := ste_vec_index.entries[i].ciphertext; + ELSE + ignored := ste_vec_index.entries[i].ciphertext; + END IF; + END LOOP; + + IF found IS NOT NULL THEN + RETURN jsonb_build_object( + 'k', 'ct', + 'c', found, + 'o', NULL, + 'm', NULL, + 'u', NULL, + 'i', col->'i', + 'v', 1 + ); + ELSE + RETURN NULL; + END IF; +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS cs_ste_vec_terms_v1(col jsonb, selector jsonb); + +CREATE FUNCTION cs_ste_vec_terms_v1(col jsonb, selector jsonb) +RETURNS cs_ste_vec_encrypted_term_v1[] AS $$ +DECLARE + ste_vec_index cs_ste_vec_index_v1; + target_selector text; + found cs_ste_vec_encrypted_term_v1; + ignored cs_ste_vec_encrypted_term_v1; + i integer; + term_array cs_ste_vec_encrypted_term_v1[]; +BEGIN + ste_vec_index := cs_ste_vec_v1(col); + target_selector := selector->>'svs'; + + FOR i IN 1..array_length(ste_vec_index.entries, 1) LOOP + -- The ELSE part is to help ensure constant time operation. + -- The result is thrown away. + IF ste_vec_index.entries[i].tokenized_selector = target_selector THEN + found := ste_vec_index.entries[i].term; + term_array := array_append(term_array, found); + ELSE + ignored := ste_vec_index.entries[i].term; + END IF; + END LOOP; + + RETURN term_array; +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS cs_ste_vec_term_v1(col jsonb, selector jsonb); + +-- col: already encrypted payload +-- selector: already encrypted payload +-- returns a value that can be used for comparison operations +CREATE OR REPLACE FUNCTION cs_ste_vec_term_v1(col jsonb, selector jsonb) +RETURNS cs_ste_vec_encrypted_term_v1 AS $$ +DECLARE + ste_vec_index cs_ste_vec_index_v1; + target_selector text; + found cs_ste_vec_encrypted_term_v1; + ignored cs_ste_vec_encrypted_term_v1; + i integer; +BEGIN + ste_vec_index := cs_ste_vec_v1(col); + target_selector := selector->>'svs'; + + FOR i IN 1..array_length(ste_vec_index.entries, 1) LOOP + -- The ELSE part is to help ensure constant time operation. + -- The result is thrown away. + IF ste_vec_index.entries[i].tokenized_selector = target_selector THEN + found := ste_vec_index.entries[i].term; + ELSE + ignored := ste_vec_index.entries[i].term; + END IF; + END LOOP; + + RETURN found; +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS cs_ste_vec_term_v1(col jsonb); + +CREATE FUNCTION cs_ste_vec_term_v1(col jsonb) +RETURNS cs_ste_vec_encrypted_term_v1 AS $$ +DECLARE + ste_vec_index cs_ste_vec_index_v1; +BEGIN + ste_vec_index := cs_ste_vec_v1(col); + RETURN ste_vec_index.entries[1].term; +END; +$$ LANGUAGE plpgsql; + -- Determine if a == b (ignoring ciphertext values) -DROP FUNCTION IF EXISTS ste_vec_v1_entry_eq(a ste_vec_v1_entry, b ste_vec_v1_entry); +DROP FUNCTION IF EXISTS cs_ste_vec_v1_entry_eq(a cs_ste_vec_v1_entry, b cs_ste_vec_v1_entry); -CREATE FUNCTION ste_vec_v1_entry_eq(a ste_vec_v1_entry, b ste_vec_v1_entry) +CREATE FUNCTION cs_ste_vec_v1_entry_eq(a cs_ste_vec_v1_entry, b cs_ste_vec_v1_entry) RETURNS boolean AS $$ DECLARE sel_cmp int; @@ -50,7 +334,7 @@ BEGIN RETURN result; END IF; FOR i IN 1..array_length(b.entries, 1) LOOP - intermediate_result := ste_vec_v1_entry_array_contains_entry(a.entries, b.entries[i]); + intermediate_result := cs_ste_vec_v1_entry_array_contains_entry(a.entries, b.entries[i]); result := result AND intermediate_result; END LOOP; RETURN result; @@ -58,9 +342,9 @@ END; $$ LANGUAGE plpgsql; -- Determine if a contains b (ignoring ciphertext values) -DROP FUNCTION IF EXISTS ste_vec_v1_entry_array_contains_entry(a ste_vec_v1_entry[], b ste_vec_v1_entry); +DROP FUNCTION IF EXISTS cs_ste_vec_v1_entry_array_contains_entry(a cs_ste_vec_v1_entry[], b cs_ste_vec_v1_entry); -CREATE FUNCTION ste_vec_v1_entry_array_contains_entry(a ste_vec_v1_entry[], b ste_vec_v1_entry) +CREATE FUNCTION cs_ste_vec_v1_entry_array_contains_entry(a cs_ste_vec_v1_entry[], b cs_ste_vec_v1_entry) RETURNS boolean AS $$ DECLARE result boolean; @@ -95,8 +379,8 @@ DROP FUNCTION IF EXISTS jsonb_to_cs_ste_vec_index_v1(input jsonb); CREATE FUNCTION jsonb_to_cs_ste_vec_index_v1(input jsonb) RETURNS cs_ste_vec_index_v1 AS $$ DECLARE - vec_entry ste_vec_v1_entry; - entry_array ste_vec_v1_entry[]; + vec_entry cs_ste_vec_v1_entry; + entry_array cs_ste_vec_v1_entry[]; entry_json jsonb; entry_json_array jsonb[]; entry_array_length int; @@ -106,9 +390,9 @@ BEGIN LOOP vec_entry := ROW( entry_json->>0, - ROW((entry_json->>1)::bytea)::ore_cllw_8_v1, + ROW(decode(entry_json->>1, 'hex'))::cs_ste_vec_encrypted_term_v1, entry_json->>2 - )::ste_vec_v1_entry; + )::cs_ste_vec_v1_entry; entry_array := array_append(entry_array, vec_entry); END LOOP; From 3766a284a1127b5e4a95d3a3f4c7e5fba6dfc7c8 Mon Sep 17 00:00:00 2001 From: Drew Thomas Date: Tue, 29 Oct 2024 18:28:26 +1100 Subject: [PATCH 3/4] Add drop statements for ore_cllw_8_variable_v1 and cs_ste_vec_encrypted_term_v1 --- sql/666-drop_types.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sql/666-drop_types.sql b/sql/666-drop_types.sql index dac8702..b494f36 100644 --- a/sql/666-drop_types.sql +++ b/sql/666-drop_types.sql @@ -4,3 +4,5 @@ DROP TYPE IF EXISTS ore_64_8_v1_term; DROP TYPE IF EXISTS cs_ste_vec_index_v1; DROP TYPE IF EXISTS ste_vec_v1_entry; DROP TYPE IF EXISTS ore_cllw_8_v1; +DROP TYPE IF EXISTS ore_cllw_8_variable_v1; +DROP TYPE IF EXISTS cs_ste_vec_encrypted_term_v1; From 98890693ce791292b1dffd7441e3b67f73a5d271 Mon Sep 17 00:00:00 2001 From: Drew Thomas Date: Tue, 29 Oct 2024 18:30:33 +1100 Subject: [PATCH 4/4] Update build for cipherstash-encrypt.sql to not include `ALTER DOMAIN` statements at top of file These were causing cipherstash-encrypt.sql to fail if the domains being altered didn't already exist. Instead, only include `ALTER DOMAIN` statements at the top of the uninstall script. --- justfile | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/justfile b/justfile index 084f7f9..7e4a6a6 100644 --- a/justfile +++ b/justfile @@ -32,26 +32,33 @@ build: # Collect all the drops # In reverse order (tac) so that we drop the constraints before the tables - grep -h -E '^(DROP|ALTER DOMAIN [^ ]+ DROP CONSTRAINT)' sql/0*-*.sql | tac > release/cipherstash-encrypt-tmp-drop.sql + grep -h -E '^(DROP)' sql/0*-*.sql | tac > release/cipherstash-encrypt-tmp-drop-install.sql # types are always last - cat sql/666-drop_types.sql >> release/cipherstash-encrypt-tmp-drop.sql + cat sql/666-drop_types.sql >> release/cipherstash-encrypt-tmp-drop-install.sql # Build cipherstash-encrypt.sql # drop everything first - cat release/cipherstash-encrypt-tmp-drop.sql > release/cipherstash-encrypt.sql + cat release/cipherstash-encrypt-tmp-drop-install.sql > release/cipherstash-encrypt.sql # cat the rest of the sql files cat sql/0*-*.sql >> release/cipherstash-encrypt.sql + # Collect all the drops + # In reverse order (tac) so that we drop the constraints before the tables + grep -h -E '^(DROP|ALTER DOMAIN [^ ]+ DROP CONSTRAINT)' sql/0*-*.sql | tac > release/cipherstash-encrypt-tmp-drop-uninstall.sql + # types are always last + cat sql/666-drop_types.sql >> release/cipherstash-encrypt-tmp-drop-uninstall.sql + # Build cipherstash-encrypt-uninstall.sql # prepend the drops to the main sql file - cat release/cipherstash-encrypt-tmp-drop.sql >> release/cipherstash-encrypt-uninstall.sql + cat release/cipherstash-encrypt-tmp-drop-uninstall.sql >> release/cipherstash-encrypt-uninstall.sql # uninstall renames configuration table cat sql/666-rename_configuration_table.sql >> release/cipherstash-encrypt-uninstall.sql # remove the drop file - rm release/cipherstash-encrypt-tmp-drop.sql + rm release/cipherstash-encrypt-tmp-drop-install.sql + rm release/cipherstash-encrypt-tmp-drop-uninstall.sql reset: