Problem
The ORE block comparator (eql_v3_internal.compare_ore_block_256_term(s), behind every ordered ORE operator and the ORE operator class) takes time that depends on where its operands first differ. A party who can time queries but cannot read the stored ciphertexts, such as an application user, can learn how long a prefix their query value shares with stored values. The constant-time comparator in ore.rs exists to keep exactly that from them.
- Text (multi-term) values. The array comparator stops at the first unequal term. On PostgreSQL 17.11 (aarch64), a six-term comparison takes about 11 µs when the first term differs and 47 µs when only the sixth does (Welch t below −1500).
- Single-term values. Each block after the first difference runs extra statements, and the
OR short-circuit skips a comparison on each of those blocks. On the machine measured the two effects nearly cancel (t = +4.9 and +13.9). The extra statements alone give t = +388, about 0.7 µs per comparison. Nothing guarantees the cancellation on other hardware or Postgres builds.
PL/pgSQL is not a constant-time environment, so the realistic target is a constant operation count: the same statements and function calls on every path, whatever the operands.
Fix
#1148 rewrites both comparators to a constant operation count (bitmask of differing blocks, branch-free latch, exactly one encrypt() on every path, multi-term values scanned in full) and adds a timing harness at packages/eql/tests/timing/ore_block_256/. After the change: single-term t = +1.8 and +1.1 at about 3–8% cost; six-term t = +4.5 and +2.7 at about 31 µs per comparison, always.
Follow-up
Apply the same pattern to the ORE v2 comparator when it lands in EQL.
Problem
The ORE block comparator (
eql_v3_internal.compare_ore_block_256_term(s), behind every ordered ORE operator and the ORE operator class) takes time that depends on where its operands first differ. A party who can time queries but cannot read the stored ciphertexts, such as an application user, can learn how long a prefix their query value shares with stored values. The constant-time comparator in ore.rs exists to keep exactly that from them.ORshort-circuit skips a comparison on each of those blocks. On the machine measured the two effects nearly cancel (t = +4.9 and +13.9). The extra statements alone give t = +388, about 0.7 µs per comparison. Nothing guarantees the cancellation on other hardware or Postgres builds.PL/pgSQL is not a constant-time environment, so the realistic target is a constant operation count: the same statements and function calls on every path, whatever the operands.
Fix
#1148 rewrites both comparators to a constant operation count (bitmask of differing blocks, branch-free latch, exactly one
encrypt()on every path, multi-term values scanned in full) and adds a timing harness atpackages/eql/tests/timing/ore_block_256/. After the change: single-term t = +1.8 and +1.1 at about 3–8% cost; six-term t = +4.5 and +2.7 at about 31 µs per comparison, always.Follow-up
Apply the same pattern to the ORE v2 comparator when it lands in EQL.