Repository navigation
Stack Encrypt cannot order floats, dates, timestamps or decimals, and its integer order keys predate ADR-0002: derive every order term over vitaminc-ore #1236
Description
Background
Stack Encrypt is CipherStash's Rust encryption engine, also used by the Go SDK and data plans. Next to a ciphertext it can store order terms, which let the database answer <, >, BETWEEN and ORDER BY without decrypting. There are two schemes: ORE (order-revealing encryption) and OPE (order-preserving encryption, compared as plain bytes). ADR-0002 (accepted) says how each kind of value is turned into an order key before it is encrypted, and vitaminc 0.6 ships those encodings in vitaminc-ore.
Problem
- Floats, dates, timestamps and decimals cannot be ordered. The ordering operators (
Lt,Lte,Gt,Gte,Between) are answered only on integers, plus text once Stack Encrypt cannot order text: answer Lt, Gt and Between on text over ADR-0002's folded order key #1234 lands. That holds on the typed path (op::Ordered,packages/stack-encrypt/src/op/mod.rs:381) and on the data-plan path (ordered(kind),packages/stack-encrypt/src/dynamic/query.rs:193). Afloat64field with an order index stores terms nothing can query. - Integer order terms don't follow ADR-0002. They come from cllw-ore's own integer encoding (
OreTerm<T: CllwOreEncrypt>,packages/stack-encrypt/src/sem/mod.rs:1113), not vitaminc-ore's. ADR-0002 notes the two differ, and wants an integer column's key at the column's declared width so values from every language land in one domain.
Impact: about 13 EQL v4 types (the *Ord and *OrdOpe types of the integer, bigint, real, double, date, timestamp and numeric families) stay unproducible. That is because the engine cannot derive their order terms the way ADR-0002 says, and not because the encryption scheme is missing.
Proposal
Unblocked for CLLW ORE and OPE (2026-10-18). The ORE crates own how each type is encoded for ordering. cllw-ore 0.5.2-rc.1, a pre-release, implements vitaminc-ore 0.6's OrderScheme behind its vitaminc feature and uses its encodings internally: the integer at its declared width, the canonical float (-0.0 as +0.0, one NaN above +Infinity), and text's fold. Stack Encrypt passes each value through and names its type's target. Block ORE still waits on an ore-rs release (#1132).
With the crate available:
- Take
cllw-ore0.5.2-rc.1 with thevitamincfeature, and pass every value straight to its scheme. Integer order terms change bytes; none are deployed (ADR-0002). - Answer the ordering operators on floats:
op::Orderedforf64, and the data path'sfloat32andfloat64fields. Anf32is ordered as thef64it widens to: its target isF64, which vitaminc widens exactly.Between's inverted-range refusal compares the bounds in the crate's order. Dates, timestamps and decimals follow once those kinds exist (Go SDK:int16is widened toInt32, and there's no way to encrypt a date, timestamp, decimal or 128-bit integer #1144, stashgen refuses every number, date and boolean EQL type — produce them in EQL v4 using ADR-0002's encodings #1130). - Pin the term bytes per kind in fixtures that the typed and data-plan paths both match, and prove the order across kinds:
-0.0 == 0.0, and NaN sorting last.
#1242 implements this. It was reworked on 2026-10-18 on top of #1235 to pass values straight through cllw-ore's vitaminc schemes; its first draft encoded vitaminc-ore keys in Stack Encrypt in front of cllw-ore 0.5. Its tests (float order, the cross-path fixture, the Go property tests) carried over.
Relationship to other work
- Stack Encrypt cannot order text: answer Lt, Gt and Between on text over ADR-0002's folded order key #1234: the same work for text.
- stashgen refuses every number, date and boolean EQL type — produce them in EQL v4 using ADR-0002's encodings #1130, stashgen refuses TextMatch, TextSearch, TextOrd and TextOrdOpe — the engine derives their terms, but no EQL type is built from them #1131: produce the EQL types once the engine derives their terms.
- stashgen refuses TextOrdOre and TextSearchOre — EQL stores block ORE terms and the engine derives CLLW ORE #1132: the
*OrdOretypes also need block ORE (separate, waits onore-rs). - Go SDK:
int16is widened toInt32, and there's no way to encrypt a date, timestamp, decimal or 128-bit integer #1144: the Go side of the new kinds.
Activity
feat(stack-encrypt,golang): add the #1144 value kinds as vitaminc leaves
docs(stack-encrypt,golang): document the #1144 value kinds
feat(stack-encrypt,golang): add the #1144 value kinds as vitaminc leaves
docs(stack-encrypt,golang): document the #1144 value kinds
feat(stack-encrypt,golang): add the #1144 value kinds as vitaminc leaves
docs(stack-encrypt,golang): document the #1144 value kinds
feat(stack-encrypt,golang): add the #1144 value kinds as vitaminc leaves
docs(stack-encrypt,golang): document the #1144 value kinds
feat(stack-encrypt,golang): add the #1144 value kinds as vitaminc leaves
docs(stack-encrypt,golang): document the #1144 value kinds
feat(eql): give EQL v4 range query domains and eql_v4.between
feat(eql): give EQL v4 range query domains and eql_v4.between
feat(stack-encrypt)!: order floats through cllw-ore's F64 target
feat(eql): give EQL v4 range query domains and eql_v4.between
feat(stack-encrypt)!: order floats through cllw-ore's F64 target
coderdan commentedon Oct 18, 2026
#1242 covers part of this issue: floats now order over vitaminc-ore (
f32/f64at cllw-ore'sF64target, ordered as PostgreSQL'sfloat8does), and #1235 moved every integer and text order term onto cllw-ore's vitaminc schemes. #1242 no longer closes this issue.Still open here:
feat(stack-encrypt)!: order floats through cllw-ore's F64 target
feat(stack-encrypt)!: order floats through cllw-ore's F64 target
feat(stack-encrypt)!: order floats through cllw-ore's F64 target