feat: support more primitives#72
Merged
freshtonic merged 11 commits intomainfrom May 5, 2026
Merged
Conversation
auxesis
approved these changes
May 5, 2026
Contributor
auxesis
left a comment
There was a problem hiding this comment.
@freshtonic walked me through this on a call, and it looks good.
Replace the per-type free `to_orderable_bytes` functions with impls of a new top-level `ToOrderableBytes` trait that exposes the encoded length as an associated `const ENCODED_LEN` and the byte array as an associated `type Bytes: AsRef<[u8]>`. Migrate the in-tree `ore-rs` consumer to the trait API.
Adds a `numeric` module with `ToOrderableBytes` impls for the signed integer primitives `i16`, `i32`, `i64` and the IEEE 754 double `f64`, each emitting the type's native byte width: - Integers: sign-flip the top bit, then big-endian. Moves negatives below positives in lex order while preserving order within each sign class. - f64: standard IEEE 754 monotonic mapping (flip all bits for negatives, sign bit only for positives), with `-0.0` canonicalised to `+0.0` so the two share an encoding. NaN handling is unspecified (NaN is unordered under `PartialOrd`). Mirrors the `IntoOrePlaintext<u64>` impls in cipherstash-suite::ope_indexer::conversion, but at native widths rather than always widening to u64.
Match the `IntoOrePlaintext<u64>` widening used by the cipherstash-suite ORE indexer: sign-flip at native width, then zero-extend to `u64` before BE serialisation. All four primitive impls now return `[u8; 8]` so they share the same downstream ORE ciphertext shape.
Adds a `bool` impl in the `numeric` module, padded to `[u8; 8]` to match the other primitive impls. `false` encodes as `[0; 8]` and `true` as `[0, 0, 0, 0, 0, 0, 0, 1]`, mirroring the `IntoOrePlaintext<u64>` impl in cipherstash-suite::ope_indexer (`OrePlaintext(*x as u64)`).
The module now hosts a `bool` impl alongside the integer and float impls; `primitive` describes the contents more accurately than `numeric`.
Extends the `primitive` module with four more impls: - `u8` → `[u8; 8]`, zero-extended to `u64` BE. - `i8` → `[u8; 8]`, sign-flipped at `u8` width then zero-extended. - `u128` → `[u8; 16]`, native BE (already lex-ordered, no sign-flip). - `i128` → `[u8; 16]`, sign-flipped at `u128` width then native BE. The 8-bit pair shares the `[u8; 8]` width with `bool`/`i16`/`i32`/ `i64`/`f64` so they all route through the same downstream ORE ciphertext shape. The 128-bit pair uses native width since there's no wider standard integer type to pad to.
Padding to a fixed `[u8; 8]` is the consumer's concern, not the encoding's: ORE constructions that need a uniform 8-byte plaintext should zero-extend upstream of the encrypter (widening is monotonic on lex order and preserves the encoding's guarantees), while OPE schemes can consume the native width directly. Reverts the earlier widen-to-`[u8; 8]` decision for `bool`, `u8`, `i8`, `i16`, `i32`. New widths: - `bool`, `u8`, `i8` → `[u8; 1]` - `i16` → `[u8; 2]` - `i32` → `[u8; 4]` `i64`, `u128`, `i128`, `f64` were already at native width.
Adds the remaining native unsigned integer widths so the trait covers every primitive listed in the module doc. Each impl is the no-op big-endian path used for u8/u128 (already in lex order). Also adds a dedicated `bool` subsection to the module doc for symmetry with the other per-type encoding-strategy paragraphs.
`char` encodes as the big-endian bytes of its `u32` Unicode scalar value — Rust's `Ord` for `char` is by code point and surrogates aren't representable, so the native u32 lex order is exactly what we need. `f32` reuses the f64 monotonic mapping (sign-bit flip for positives, all-bits flip for negatives, -0.0 canonicalised to +0.0) narrowed to u32. Module doc gains a `char` section and folds the float docs into a single IEEE 754 section covering both widths.
Migrates the existing `u32`/`u64`/`f64` `OreEncrypt` impls to call `orderable_bytes::ToOrderableBytes::to_orderable_bytes()` and adds impls for the remaining 11 primitives covered by the trait: `bool`, `u8`/`u16`/`u128`, `i8`/`i16`/`i32`/`i64`/`i128`, `char`, and `f32`. The new `f64` byte path is bit-for-bit identical to the previous `ToOrderedInteger::map_to::<u64>` mapping (same sign-bit canonicalisation, same XOR mask, same `-0.0 → +0.0` collapse), so on-disk ciphertexts remain compatible. Each impl follows the same trait-driven shape used by the chrono and decimal consumers, with encoded lengths lifted into module-level `const`s because stable Rust won't accept `<Self as ToOrderableBytes>::ENCODED_LEN` directly in const-generic position.
`ToOrderedInteger` / `FromOrderedInteger` were the legacy bridge from `f64` to a lex-orderable `u64` plaintext. Now that `OreEncrypt for f64` goes through `orderable_bytes::ToOrderableBytes`, the trait and its sole impl are unused. Remove the module and its `mod convert;` entry.
1b067cf to
3d27b74
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ToOrderableBytestrait inpackages/orderable-bytes/src/lib.rsthat replaces the per-type freeto_orderable_bytesfunctions with a single trait carryingconst ENCODED_LEN: usize,type Bytes: AsRef<[u8]>, andfn to_orderable_bytes(&self) -> Self::Bytes. Existingchronoanddecimalmodules and the in-treeore-rsconsumer are migrated to the trait API.primitivemodule (packages/orderable-bytes/src/primitive.rs) with 14ToOrderableBytesimpls coveringbool,char, every native unsigned and signed integer (u8–u128,i8–i128), and both IEEE 754 floats (f32,f64).ore-rsend-to-end:OreEncryptnow has impls for all 14 primitives inpackages/ore-rs/src/encrypt.rs, all routing throughToOrderableBytes. The previously-bespokeu32/u64/f64OreEncryptimpls are migrated to the same path so the file is uniform withchrono.rs/decimal.rs. The legacyToOrderedInteger/FromOrderedIntegerbridge inpackages/ore-rs/src/convert.rsbecomes dead code and is removed.[u8; 8]plaintext blocks) can zero-extend upstream of the encrypter; widening is monotonic on lex order so the encoding's guarantees are preserved.Encoding strategies
bool—false → 0x00,true → 0x01(already in lex order)char— big-endian bytes of*self as u32. Rust'sOrdforcharis by code point and surrogates are not representable, so the nativeu32lex order is exactly the order we need.f32/f64— branchless monotonic mapping: negatives flip every bit, positives flip just the sign bit.-0.0is canonicalised to+0.0before encoding so byte equality matches-0.0 == 0.0. NaN handling is unspecified — the trait's order/equality contract only applies to non-NaN inputs.Compatibility
The new
f64byte encoding is bit-for-bit identical to the legacyToOrderedInteger::map_to::<u64>().to_be_bytes()output (same sign-bit canonicalisation, same XOR mask, same-0.0 → +0.0collapse), so existingf64ciphertexts on disk remain comparable against ciphertexts produced by this branch.Test plan
cargo test -p orderable-bytes— 32 unit tests inprimitive.rs(known-anchor + ascending-order pairs per type, plus0.0/-0.0/subnormal coverage for both floats);--all-featuresbrings the total to 54 withchronoanddecimalquickcheck/property tests passing under the migrated trait APIcargo test -p ore-rs --all-features— 53 unit tests + 5 doc-tests pass, including thesigned_zeros_compare_equalregression test that pins(-0.0).encrypt(&ore) == 0.0.encrypt(&ore)cargo fmt --check --all— cleanore-rsconsumers (chrono.rs,decimal.rs,encrypt.rs) all call.to_orderable_bytes()via the trait; no per-type bespoke encoding paths remain (verified: zeroto_be_bytes/map_tocallers in those files)charordering test spans ASCII, BMP, and supplementary planes (crosses the surrogate gap fromU+D7FFtoU+E000)f32/f64subnormal tests confirm strict ordering:0.0 < f*::from_bits(1) < f*::MIN_POSITIVE