Skip to content

Commit dafbcce

Browse files
committed
Encrypted JSONB operators and functions
1 parent 71c86bb commit dafbcce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+3229
-1591
lines changed

diagrams/overview-insert.drawio.svg

Lines changed: 0 additions & 457 deletions
This file was deleted.

diagrams/overview-select.drawio.svg

Lines changed: 0 additions & 552 deletions
This file was deleted.

src/blake3/functions.sql

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- REQUIRE: src/schema.sql
2+
-- REQUIRE: src/mac/types.sql
3+
4+
5+
-- extracts ste_vec index from a jsonb value
6+
DROP FUNCTION IF EXISTS eql_v1.blake3(val jsonb);
7+
8+
-- extracts blake3 index from a jsonb value
9+
DROP FUNCTION IF EXISTS eql_v1.blake3(val jsonb);
10+
11+
CREATE FUNCTION eql_v1.blake3(val jsonb)
12+
RETURNS eql_v1.blake3
13+
IMMUTABLE STRICT PARALLEL SAFE
14+
AS $$
15+
BEGIN
16+
17+
IF NOT (val ? 'b') NULL THEN
18+
RAISE 'Expected a blake3 index (b) value in json: %', val;
19+
END IF;
20+
21+
IF val->>'b' IS NULL THEN
22+
RETURN NULL;
23+
END IF;
24+
25+
RETURN val->>'b';
26+
END;
27+
$$ LANGUAGE plpgsql;
28+
29+
30+
-- extracts blake3 index from an eql_v1_encrypted value
31+
DROP FUNCTION IF EXISTS eql_v1.blake3(val eql_v1_encrypted);
32+
33+
CREATE FUNCTION eql_v1.blake3(val eql_v1_encrypted)
34+
RETURNS eql_v1.blake3
35+
IMMUTABLE STRICT PARALLEL SAFE
36+
AS $$
37+
BEGIN
38+
RETURN (SELECT eql_v1.blake3(val.data));
39+
END;
40+
$$ LANGUAGE plpgsql;

src/blake3/types.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- REQUIRE: src/schema.sql
2+
3+
DROP DOMAIN IF EXISTS eql_v1.blake3;
4+
CREATE DOMAIN eql_v1.blake3 AS text;

src/common.sql

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,54 @@
22
-- REQUIRE: src/schema.sql
33

44

5+
-- Constant time comparison of 2 bytea values
6+
DROP FUNCTION IF EXISTS eql_v1.bytea_eq(a bytea, b bytea);
7+
8+
CREATE FUNCTION eql_v1.bytea_eq(a bytea, b bytea) RETURNS boolean AS $$
9+
DECLARE
10+
result boolean;
11+
differing bytea;
12+
BEGIN
13+
14+
-- Check if the bytea values are the same length
15+
IF LENGTH(a) != LENGTH(b) THEN
16+
RETURN false;
17+
END IF;
18+
19+
-- Compare each byte in the bytea values
20+
result := true;
21+
FOR i IN 1..LENGTH(a) LOOP
22+
IF SUBSTRING(a FROM i FOR 1) != SUBSTRING(b FROM i FOR 1) THEN
23+
result := result AND false;
24+
END IF;
25+
END LOOP;
26+
27+
RETURN result;
28+
END;
29+
$$ LANGUAGE plpgsql;
30+
31+
32+
DROP FUNCTION IF EXISTS eql_v1.jsonb_array_to_bytea_array(val jsonb);
33+
34+
-- Casts a jsonb array of hex-encoded strings to an array of bytea.
35+
CREATE FUNCTION eql_v1.jsonb_array_to_bytea_array(val jsonb)
36+
RETURNS bytea[] AS $$
37+
DECLARE
38+
terms_arr bytea[];
39+
BEGIN
40+
IF jsonb_typeof(val) = 'null' THEN
41+
RETURN NULL;
42+
END IF;
43+
44+
SELECT array_agg(decode(value::text, 'hex')::bytea)
45+
INTO terms_arr
46+
FROM jsonb_array_elements_text(val) AS value;
47+
48+
RETURN terms_arr;
49+
END;
50+
$$ LANGUAGE plpgsql;
51+
52+
553

654
--
755
-- Convenience function to log a message

src/encrypted/functions.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ AS $$
1818
END;
1919
$$ LANGUAGE plpgsql;
2020

21+
DROP FUNCTION IF EXISTS eql_v1.ciphertext(val eql_v1_encrypted);
22+
23+
CREATE FUNCTION eql_v1.ciphertext(val eql_v1_encrypted)
24+
RETURNS text
25+
IMMUTABLE STRICT PARALLEL SAFE
26+
AS $$
27+
BEGIN
28+
RETURN eql_v1.ciphertext(val.data);
29+
END;
30+
$$ LANGUAGE plpgsql;
31+
32+
2133

2234
DROP FUNCTION IF EXISTS eql_v1.to_jsonb(val eql_v1_encrypted);
2335

src/match/functions.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,3 @@ AS $$
2828
RETURN (SELECT eql_v1.match(val.data));
2929
END;
3030
$$ LANGUAGE plpgsql;
31-
32-

src/operators/->.sql

Lines changed: 27 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,47 @@
11

2+
-- REQUIRE: src/schema.sql
23
-- REQUIRE: src/encrypted/types.sql
34
-- REQUIRE: src/encrypted/functions.sql
45

56

6-
7+
--
8+
-- The -> operator returns an encrypted matching the selector
9+
-- Encyprted JSON is represented as an array of `eql_v1_encrypted`.
10+
-- Each `eql_v1_encrypted` value has a selector, ciphertext, and an index term of
11+
-- - blake3
12+
-- - ore_cllw_u64_8
13+
-- - ore_cllw_var_8
14+
--
15+
-- {
16+
-- "sv": [ {"c": "", "s": "", "b": "" } ]
17+
-- }
18+
--
719
DROP OPERATOR IF EXISTS -> (eql_v1_encrypted, text);
20+
821
DROP FUNCTION IF EXISTS eql_v1."->"(e eql_v1_encrypted, selector text);
922

10-
--
11-
-- Returns
12-
--
1323
CREATE FUNCTION eql_v1."->"(e eql_v1_encrypted, selector text)
1424
RETURNS eql_v1_encrypted
1525
IMMUTABLE STRICT PARALLEL SAFE
1626
AS $$
17-
-- DECLARE
18-
-- j jsonb;
19-
-- found: text;
20-
-- ignored: text;
27+
DECLARE
28+
sv eql_v1_encrypted[];
29+
found eql_v1_encrypted;
2130
BEGIN
2231

23-
-- j := e->'j';
24-
-- PERFORM eql_v1.log(j);
32+
IF e IS NULL THEN
33+
RETURN NULL;
34+
END IF;
2535

26-
-- FOR i IN 1..jsonb_array_length(j, 1) LOOP
27-
-- -- -- The ELSE part is to help ensure constant time operation.
28-
-- -- -- The result is thrown away.
29-
-- IF j[i]->'s' = selector THEN
30-
-- found := j[i]->'c';
31-
-- ELSE
32-
-- ignored := j[i]->'c';
33-
-- END IF;
34-
-- END LOOP;
36+
sv := eql_v1.ste_vec(e);
3537

36-
-- IF found IS NOT NULL THEN
37-
-- RETURN found;
38-
-- ELSE
39-
-- RETURN NULL;
40-
-- END IF;
41-
42-
RETURN (
43-
SELECT elem::eql_v1_encrypted
44-
FROM jsonb_array_elements(e.data->'j') AS elem
45-
WHERE elem->>'s' = selector
46-
LIMIT 1
47-
);
38+
FOR idx IN 1..array_length(sv, 1) LOOP
39+
if eql_v1.selector(sv[idx]) = selector THEN
40+
found := sv[idx];
41+
END IF;
42+
END LOOP;
4843

44+
RETURN found;
4945
END;
5046
$$ LANGUAGE plpgsql;
5147

@@ -56,35 +52,3 @@ CREATE OPERATOR ->(
5652
RIGHTARG=text
5753
);
5854

59-
60-
-- ste_vec_index := eql_v1.ste_vec(col);
61-
62-
-- IF ste_vec_index IS NULL THEN
63-
-- RETURN NULL;
64-
-- END IF;
65-
66-
-- target_selector := selector->>'svs';
67-
68-
-- FOR i IN 1..array_length(ste_vec_index.entries, 1) LOOP
69-
-- -- The ELSE part is to help ensure constant time operation.
70-
-- -- The result is thrown away.
71-
-- IF ste_vec_index.entries[i].tokenized_selector = target_selector THEN
72-
-- found := ste_vec_index.entries[i].ciphertext;
73-
-- ELSE
74-
-- ignored := ste_vec_index.entries[i].ciphertext;
75-
-- END IF;
76-
-- END LOOP;
77-
78-
-- IF found IS NOT NULL THEN
79-
-- RETURN jsonb_build_object(
80-
-- 'k', 'ct',
81-
-- 'c', found,
82-
-- 'o', NULL,
83-
-- 'm', NULL,
84-
-- 'u', NULL,
85-
-- 'i', col->'i',
86-
-- 'v', 1
87-
-- );
88-
-- ELSE
89-
-- RETURN NULL;
90-
-- END IF;

src/operators/->>.sql

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
-- REQUIRE: src/schema.sql
12
-- REQUIRE: src/encrypted/types.sql
23
-- REQUIRE: src/encrypted/functions.sql
34

45

5-
66
DROP OPERATOR IF EXISTS ->> (eql_v1_encrypted, text);
77
DROP FUNCTION IF EXISTS eql_v1."->>"(e eql_v1_encrypted, selector text);
88

@@ -11,22 +11,28 @@ CREATE FUNCTION eql_v1."->>"(e eql_v1_encrypted, selector text)
1111
IMMUTABLE STRICT PARALLEL SAFE
1212
AS $$
1313
DECLARE
14-
j jsonb;
14+
vec jsonb;
1515
found text;
1616
ignored text;
1717
BEGIN
18-
-- j := e.data->'j';
19-
-- -- PERFORM eql_v1.log(j::text);
20-
-- PERFORM eql_v1.log('jsonb_array_length(j)');
21-
-- PERFORM eql_v1.log(jsonb_array_length(j)::text);
22-
23-
-- FOR i IN 0..jsonb_array_length(j) LOOP
24-
-- -- The ELSE part is to help ensure constant time operation.
25-
-- -- The result is thrown away.
26-
-- IF j[i]->>'s' = selector THEN
27-
-- found := eql_v1.ciphertext(j->i);
18+
-- Parent StructuredVec data
19+
vec := eql_v1.ste_vec(e.data);
20+
21+
RETURN (
22+
SELECT elem::text
23+
FROM jsonb_array_elements(vec) AS elem
24+
WHERE eql_v1.selector(elem) = selector
25+
LIMIT 1
26+
);
27+
28+
--
29+
-- Constant time version
30+
--
31+
-- FOR i IN 0..jsonb_array_length(vec) LOOP
32+
-- IF vec->i->>'s' = selector THEN
33+
-- found := eql_v1.ciphertext(vec->i);
2834
-- ELSE
29-
-- ignored := eql_v1.ciphertext(j->i);
35+
-- ignored := eql_v1.ciphertext(vec->i);
3036
-- END IF;
3137
-- END LOOP;
3238

@@ -35,12 +41,7 @@ AS $$
3541
-- ELSE
3642
-- RETURN NULL;
3743
-- END IF;
38-
RETURN (
39-
SELECT eql_v1.ciphertext(elem)
40-
FROM jsonb_array_elements(e.data->'j') AS elem
41-
WHERE elem->>'s' = selector
42-
LIMIT 1
43-
);
44+
4445
END;
4546
$$ LANGUAGE plpgsql;
4647

src/operators/->>_test.sql

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
\set ON_ERROR_STOP on
2+
3+
4+
SELECT create_table_with_encrypted();
5+
SELECT seed_encrypted_json();
6+
7+
--
8+
-- The ->> operator returns an encrypted matching the selector
9+
DO $$
10+
BEGIN
11+
PERFORM assert_result(
12+
'Fetch ciphertext from encrypted column',
13+
'SELECT e->>''bca213de9ccce676fa849ff9c4807963'' FROM encrypted LIMIT 1;',
14+
'mBbLGB9xHAGzLvUj-`@Wmf=IhD87n7r3ir3n!Sk6AKir_YawR=0c>pk(OydB;ntIEXK~c>V&4>)rNkf<JN7fmlO)c^iBv;-X0+3XyK5d`&&I-oeIEOcwPf<3zy');
15+
16+
PERFORM assert_count(
17+
'Fetch ciphertext from encrypted column',
18+
'SELECT e->>''bca213de9ccce676fa849ff9c4807963'' FROM encrypted;',
19+
3);
20+
END;
21+
$$ LANGUAGE plpgsql;
22+
23+
24+
--
25+
-- encrypted returned from -> operator expression called via eql_v1.ciphertext
26+
--
27+
DO $$
28+
DECLARE
29+
result eql_v1_encrypted;
30+
BEGIN
31+
PERFORM assert_result(
32+
'Fetch ciphertext from encrypted column',
33+
'SELECT eql_v1.ciphertext(e->''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted LIMIT 1;',
34+
'mBbLGB9xHAGzLvUj-`@Wmf=IhD87n7r3ir3n!Sk6AKir_YawR=0c>pk(OydB;ntIEXK~c>V&4>)rNkf<JN7fmlO)c^iBv;-X0+3XyK5d`&&I-oeIEOcwPf<3zy');
35+
36+
PERFORM assert_count(
37+
'Fetch ciphertext from encrypted column',
38+
'SELECT eql_v1.ciphertext(e->''2517068c0d1f9d4d41d2c666211f785e'') FROM encrypted;',
39+
3);
40+
END;
41+
$$ LANGUAGE plpgsql;

0 commit comments

Comments
 (0)