Repository navigation
Conversation
coderdan
force-pushed
the
feat/orderable-bytes-variable-length
branch
from
October 8, 2026 09:15
0c222e6 to
b3b7800
Compare
coderdan
marked this pull request as ready for review
October 8, 2026 10:17
coderdan
added this pull request to stack #98
October 8, 2026 10:17
There was a problem hiding this comment.
🟡 Changes recommended
The public trait documentation incorrectly claims that per-type modules expose free encoded-length constants.
1 open finding
What changed in this PR
Adds borrowed variable-length orderable encodings while separating fixed-length metadata into a dedicated trait.
Changes:
- Adds identity encodings for strings and byte strings.
- Introduces
FixedOrderableBytesand migrates fixed-width implementations. - Expands documentation, doctests, and golden/property tests.
| File | Description |
|---|---|
packages/ore-rs/src/encrypt.rs |
Uses the new fixed-length trait. |
packages/ore-rs/src/decimal.rs |
Migrates decimal length lookup. |
packages/ore-rs/src/chrono.rs |
Migrates chrono length lookups. |
packages/orderable-bytes/tests/golden.rs |
Adds variable-length golden vectors. |
packages/orderable-bytes/src/variable.rs |
Implements borrowed string and byte encodings. |
packages/orderable-bytes/src/primitive.rs |
Splits primitive trait implementations. |
packages/orderable-bytes/src/lib.rs |
Defines the revised trait API and doctests. |
packages/orderable-bytes/src/decimal.rs |
Implements the fixed-length trait for decimals. |
packages/orderable-bytes/src/chrono.rs |
Implements the fixed-length trait for chrono types. |
packages/orderable-bytes/README.md |
Documents usage and variable-length constraints. |
packages/orderable-bytes/Cargo.toml |
Updates the package description. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
…e strings ToOrderableBytes required a fixed ENCODED_LEN, so text and raw bytes had no encoding here, and each ORE/OPE scheme encoded strings its own lossy way. vitaminc-ore needs one order encoding for every kind. ToOrderableBytes is now the general trait, with a GAT Bytes<'a> so an impl can return a borrowed slice. ENCODED_LEN moves to a new subtrait, FixedOrderableBytes, implemented by every existing type. The new variable module implements ToOrderableBytes for str, String, [u8] and Vec<u8> as the value's own bytes, borrowed: byte order is already their order (a prefix sorts first; code-point order for str), and no copy of the plaintext is made. No normalisation is applied, and the encodings do not mark where they end, so a consumer must never zero-pad or concatenate them; the module docs and README say so. Every fixed-length encoding is unchanged: the golden vectors pass as written. The README's usage example called decimal::to_orderable_bytes and decimal::ENCODED_LEN, neither of which exists; it now uses the trait. Refs #94 BREAKING CHANGE: ToOrderableBytes::ENCODED_LEN moved to FixedOrderableBytes::ENCODED_LEN, and ToOrderableBytes::Bytes is now the generic associated type Bytes<'a>. Import FixedOrderableBytes wherever ENCODED_LEN is named.
orderable-bytes moved ENCODED_LEN from ToOrderableBytes to the new FixedOrderableBytes subtrait. ore-rs names each block length through it. Its public API and ciphertexts are unchanged.
The README's usage example called decimal::to_orderable_bytes and decimal::ENCODED_LEN, which never existed, and nothing noticed because the README was not compiled. A cfg(doctest) item now includes the README as doc text, so cargo test compiles and runs its Rust blocks. The worked byte tables were unlabelled fences, which rustdoc treats as Rust; they are now marked as text. Checked by restoring the old call: the doctest fails with E0425.
Only chrono::datetime_utc exports a free ENCODED_LEN; primitive, decimal and chrono::naive_date don't. Point callers at the associated constant on FixedOrderableBytes alone. Claude-Session: https://claude.ai/code/session_012zHhtTEo968WazExKxA5oT
Making ToOrderableBytes the general trait meant 0.1 code bounded on it, which assumed a fixed length, kept compiling and silently accepted strings. Code that zero-pads to a block then makes "a" equal "a\0", and code that left-pads puts "b" before "aa". The general trait is now OrderableBytes. Every type also implements exactly one subtrait: FixedOrderableBytes (with ENCODED_LEN) or the new VariableOrderableBytes (str, String, [u8], Vec<u8>). The old name is gone, so 0.1 bounds fail to compile, and code that pads bounds on FixedOrderableBytes and rejects strings at compile time. A compile_fail doctest pins that. Also in this release: - FixedOrderableBytes documents that Bytes must be exactly ENCODED_LEN long as a requirement, not a convention. - chrono::datetime_utc::ENCODED_LEN is removed. It was the only per-module length constant; use the associated constant. - The README example uses f64 and strings, so its doctest runs without the decimal feature. The README gains a section on upgrading from 0.1. Encoded bytes are unchanged: the golden vectors pass as written. Refs #94 BREAKING CHANGE: ToOrderableBytes is renamed to OrderableBytes. Replace a T: ToOrderableBytes bound with T: FixedOrderableBytes, and import OrderableBytes wherever to_orderable_bytes is called. chrono::datetime_utc::ENCODED_LEN is removed; use <DateTime<Utc> as FixedOrderableBytes>::ENCODED_LEN. Claude-Session: https://claude.ai/code/session_012zHhtTEo968WazExKxA5oT
…length Follows the orderable-bytes rename of ToOrderableBytes to OrderableBytes. The decimal bench hard-coded a 14-byte block and cited a constant that does not exist; it now reads <Decimal as FixedOrderableBytes>::ENCODED_LEN. No change to ore-rs's API or ciphertexts. Claude-Session: https://claude.ai/code/session_012zHhtTEo968WazExKxA5oT
coderdan
force-pushed
the
feat/orderable-bytes-variable-length
branch
from
October 10, 2026 03:17
2021151 to
dd4fc2c
Compare
OrderableBytes::Bytes<'a> borrows from the value so strings encode without a copy, but that tied even a fixed-length [u8; N] to the value: generic code bounded on FixedOrderableBytes could no longer return or store the bytes, which 0.1's owned T::Bytes allowed. FixedOrderableBytes now has an owned output, type Array = [u8; N], and to_fixed_orderable_bytes(), which returns the same bytes as to_orderable_bytes() without borrowing. Binding Bytes<'a> to an owned type for every 'a was not an option: the GAT's `Self: 'a` bound makes that supertrait fail with E0311. The rules that every type implements exactly one of Fixed/Variable, and that the fixed bytes are exactly ENCODED_LEN long, were docs only. All three traits are now sealed, and one macro writes each fixed type's impls from a single length, so the two can't drift and the 14 primitive impls are no longer copy-pasted pairs. Also: - &T implements the same traits as T, so "abc" can be passed by value. - [u8; N] is fixed-length and encodes to itself, so hashes and UUIDs need no copy into a Vec<u8>. - Both compile_fail doctests name E0277, so they only pass for the error they pin. - The golden check asserts that both methods return the same bytes, and pins [u8; N]. - The README upgrade guide covers T::Bytes -> T::Array and sealing. Encoded bytes are unchanged: every golden vector passes as written. Refs #94 BREAKING CHANGE: OrderableBytes, FixedOrderableBytes and VariableOrderableBytes are sealed and can't be implemented outside this crate. FixedOrderableBytes gains the associated type Array and the method to_fixed_orderable_bytes; generic code that stores or returns fixed-length bytes should use T::Array and to_fixed_orderable_bytes() in place of T::Bytes and to_orderable_bytes(). Claude-Session: https://claude.ai/code/session_012zHhtTEo968WazExKxA5oT
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
orderable-bytesturns values into bytes whose order matches the values' order, and order-revealing (ORE) and order-preserving (OPE) encryption run on top of those bytes. Until now it covered fixed-size types only. Strings had no shared encoding, so each scheme encoded text its own way, with different and lossy rules. This PR adds an encoding for strings and byte strings, sovitaminc-orecan use one order encoding for every kind of value.Breaking (0.2.0): the traits are reorganised so that fixed-length and variable-length encodings can't be confused. Nothing about the encoded bytes changes: every golden vector from #96 passes unchanged.
Stacked on #96. Merge that first.
Traits
bool,char,[u8; N],Decimaland thechronotypes implementFixedOrderableBytes.str,String,[u8]andVec<u8>implementVariableOrderableBytes.&Timplements the same traits asT.to_orderable_bytesmay borrow from the value, so its output can't outlive it.to_fixed_orderable_bytesreturns the same bytes as an owned array, so generic code can return or store them (fn term<T: FixedOrderableBytes>(v: T) -> T::Array). BindingBytes<'a>to an owned type for every'adoesn't compile: theSelf: 'abound gives E0311.ENCODED_LENlong, because one macro writes each type's impls from a single length. Acompile_fail,E0277doctest checks that downstream impls are rejected.ToOrderableBytesmeant fixed-length. If that name had become the general trait, 0.1 code bounded on it would still compile and silently accept strings. Code that zero-pads to a block would then make"a"equal"a\0", and left-padding would put"b"before"aa". With the old name removed, those bounds fail to compile, and code that pads bounds onFixedOrderableBytes, which rejects strings. Acompile_fail,E0277doctest pins this.Bytesis a generic associated type, so variable-length impls can return a borrowed slice.Changes
variablemodule:strsorts by Unicode code point.éwritten as U+00E9 and ase+ U+0301 encode differently. Callers normalise first.impl_fixed_orderable_bytes!, writes every fixed-length type's impls. The 14 primitives are no longer copy-pasted impl pairs.[u8; N]is new and encodes to itself, so hashes and UUIDs need no copy into aVec<u8>.chrono::datetime_utc::ENCODED_LENis removed. It was the only per-module length constant; use the associated constant.FixedOrderableBytes.refactor(ore-rs)commits.T::BytesbecomingT::Array, and sealing.decimal::to_orderable_bytesanddecimal::ENCODED_LEN, neither of which exists. It now goes through the traits and usesf64and strings.cfg(doctest)item includes the README). They need no optional feature, so a plaincargo testruns them.String's andVec<u8>'s ownOrd/Eq;strand bytes;VariableOrderableBytes;compile_fail,E0277doctests:Stringcan't be passed to a pad-to-block function, and a downstream type can't implementOrderableBytes;&Tencodes asTunder all three traits;[u8; N]: golden rows and a quickcheck property that its order matches array order;to_orderable_bytesandto_fixed_orderable_bytesreturn the same bytes.Verification
cargo test --all-featurespasses on stable and on 1.78.0 (152 tests), andcargo test -p orderable-bytespasses with default features, including the README andcompile_faildoctests. One README block isrust,ignore; it is the 0.1 code in the upgrade guide.cargo clippy --no-deps --all-targets --all-features -- -D warningspasses on stable and 1.78.cargo fmt -- --checkandcargo docwith-D warningsare clean.cargo bench -p ore-rs --bench decimal --no-runbuilds.Related
orderable-byteshas no variable-length encoding — text and bytes can't share the order layer #94vitaminc-ore(Newvitaminc-orecrate: one order encoding per kind, with block ORE, CLLW ORE and CLLW OPE as pluggable schemes vitaminc#374) depends on 0.2.Review notes
!commits. The pending release PR chore: release #88 (0.1.2) predates this.refactor(ore-rs)commits, so ore-rs should get a patch release, not 0.9.0. Itsorderable-bytes = "^0.1"requirement can't be raised to 0.2 in this PR, because the path crate is still 0.1.1 until release-plz bumps it. release-plz updates it at release time. Only a manualcargo publishbefore then would fail.feat(orderable-bytes)!commits don't build ore-rs on their own; therefactor(ore-rs)commit after each one fixes it. This is deliberate, so that release-plz doesn't attribute a breaking change to ore-rs.