Skip to content

Commit df8ef42

Browse files
chore: use lexicographic ordering for Word (#847)
* chore: use lexicographic ordering for `Word` * chore: add changelog * chore: add note on word ordering --------- Co-authored-by: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com>
1 parent ae56ff1 commit df8ef42

File tree

4 files changed

+45
-48
lines changed

4 files changed

+45
-48
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 0.22.3 (unreleased)
1+
## 0.22.3 (2026-02-23)
22

33
- Refactored to introduce a unified `Felt` type for on-chain and off-chain code ([#819](https://github.com/0xMiden/crypto/pull/819)).
4+
- Change `Ord for Word` to use lexicographic ordering ([#847](https://github.com/0xMiden/crypto/pull/847)).
45
- Add `From<{u8, u16, u32}> for Felt` and `TryFrom<u64> for Felt` ([#848](https://github.com/0xMiden/crypto/pull/848)).
56

67
## 0.22.2 (2026-02-01)

miden-field/src/word/lexicographic.rs

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,7 @@ impl<T: Into<Word> + Copy> Ord for LexicographicWord<T> {
7070
let self_word: Word = self.0.into();
7171
let other_word: Word = other.0.into();
7272

73-
for (felt0, felt1) in self_word
74-
.iter()
75-
.rev()
76-
.map(Felt::as_canonical_u64)
77-
.zip(other_word.iter().rev().map(Felt::as_canonical_u64))
78-
{
79-
let ordering = felt0.cmp(&felt1);
80-
if let Ordering::Less | Ordering::Greater = ordering {
81-
return ordering;
82-
}
83-
}
84-
85-
Ordering::Equal
73+
self_word.cmp(&other_word)
8674
}
8775
}
8876

@@ -132,31 +120,6 @@ mod tests {
132120
}
133121
}
134122

135-
#[test]
136-
fn lexicographic_word_ordering() {
137-
for (expected, key0, key1) in [
138-
(Ordering::Equal, [0, 0, 0, 0u32], [0, 0, 0, 0u32]),
139-
(Ordering::Greater, [1, 0, 0, 0u32], [0, 0, 0, 0u32]),
140-
(Ordering::Greater, [0, 1, 0, 0u32], [0, 0, 0, 0u32]),
141-
(Ordering::Greater, [0, 0, 1, 0u32], [0, 0, 0, 0u32]),
142-
(Ordering::Greater, [0, 0, 0, 1u32], [0, 0, 0, 0u32]),
143-
(Ordering::Less, [0, 0, 0, 0u32], [1, 0, 0, 0u32]),
144-
(Ordering::Less, [0, 0, 0, 0u32], [0, 1, 0, 0u32]),
145-
(Ordering::Less, [0, 0, 0, 0u32], [0, 0, 1, 0u32]),
146-
(Ordering::Less, [0, 0, 0, 0u32], [0, 0, 0, 1u32]),
147-
(Ordering::Greater, [0, 0, 0, 1u32], [1, 1, 1, 0u32]),
148-
(Ordering::Greater, [0, 0, 1, 0u32], [1, 1, 0, 0u32]),
149-
(Ordering::Less, [1, 1, 1, 0u32], [0, 0, 0, 1u32]),
150-
(Ordering::Less, [1, 1, 0, 0u32], [0, 0, 1, 0u32]),
151-
] {
152-
assert_eq!(
153-
LexicographicWord::from(key0.map(Felt::from_u32))
154-
.cmp(&LexicographicWord::from(key1.map(Felt::from_u32))),
155-
expected
156-
);
157-
}
158-
}
159-
160123
#[test]
161124
fn lexicographic_serialization() {
162125
let word = Word::from([1u64, 2, 3, 4].map(Felt::new));

miden-field/src/word/mod.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ mod tests;
3535
// ================================================================================================
3636

3737
/// A unit of data consisting of 4 field elements.
38+
///
39+
/// For ordering a word with `Ord` the word's elements are treated as limbs of an integer
40+
/// in little-endian limb order and thus comparison starts from the most significant element.
3841
#[derive(Default, Copy, Clone, Eq, PartialEq)]
3942
#[cfg_attr(
4043
not(all(target_family = "wasm", miden)),
@@ -306,10 +309,10 @@ impl IndexMut<Range<usize>> for Word {
306309

307310
impl Ord for Word {
308311
fn cmp(&self, other: &Self) -> Ordering {
309-
// Compare the canonical u64 representation of both elements.
312+
// Compare the canonical u64 representation of both words.
310313
//
311-
// It will iterate the elements and will return the first computation different than
312-
// `Equal`. Otherwise, the ordering is equal.
314+
// It will iterate the elements in reverse and will return the first computation different
315+
// than `Equal`. Otherwise, the ordering is equal.
313316
//
314317
// We use `as_canonical_u64()` to ensure we're comparing the actual field element values
315318
// in their canonical form (that is, `x in [0,p)`). P3's Goldilocks field uses unreduced
@@ -319,14 +322,19 @@ impl Ord for Word {
319322
// We must iterate over and compare each element individually. A simple bytestring
320323
// comparison would be inappropriate because the `Word`s are represented in
321324
// "lexicographical" order.
322-
self.as_elements_array()
325+
for (felt0, felt1) in self
323326
.iter()
327+
.rev()
324328
.map(Felt::as_canonical_u64)
325-
.zip(other.as_elements_array().iter().map(Felt::as_canonical_u64))
326-
.fold(Ordering::Equal, |ord, (a, b)| match ord {
327-
Ordering::Equal => a.cmp(&b),
328-
_ => ord,
329-
})
329+
.zip(other.iter().rev().map(Felt::as_canonical_u64))
330+
{
331+
let ordering = felt0.cmp(&felt1);
332+
if let Ordering::Less | Ordering::Greater = ordering {
333+
return ordering;
334+
}
335+
}
336+
337+
Ordering::Equal
330338
}
331339
}
332340

miden-field/src/word/tests.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use alloc::{collections::BTreeMap, string::String, vec::Vec};
2+
use core::cmp::Ordering;
23

34
use miden_serde_utils::SliceReader;
45
use proptest::prelude::*;
@@ -259,3 +260,27 @@ proptest! {
259260
prop_assert_eq!(map.get(&word), Some(&2));
260261
}
261262
}
263+
264+
#[test]
265+
fn word_is_ordered_lexicographically() {
266+
for (expected, key0, key1) in [
267+
(Ordering::Equal, [0, 0, 0, 0u32], [0, 0, 0, 0u32]),
268+
(Ordering::Greater, [1, 0, 0, 0u32], [0, 0, 0, 0u32]),
269+
(Ordering::Greater, [0, 1, 0, 0u32], [0, 0, 0, 0u32]),
270+
(Ordering::Greater, [0, 0, 1, 0u32], [0, 0, 0, 0u32]),
271+
(Ordering::Greater, [0, 0, 0, 1u32], [0, 0, 0, 0u32]),
272+
(Ordering::Less, [0, 0, 0, 0u32], [1, 0, 0, 0u32]),
273+
(Ordering::Less, [0, 0, 0, 0u32], [0, 1, 0, 0u32]),
274+
(Ordering::Less, [0, 0, 0, 0u32], [0, 0, 1, 0u32]),
275+
(Ordering::Less, [0, 0, 0, 0u32], [0, 0, 0, 1u32]),
276+
(Ordering::Greater, [0, 0, 0, 1u32], [1, 1, 1, 0u32]),
277+
(Ordering::Greater, [0, 0, 1, 0u32], [1, 1, 0, 0u32]),
278+
(Ordering::Less, [1, 1, 1, 0u32], [0, 0, 0, 1u32]),
279+
(Ordering::Less, [1, 1, 0, 0u32], [0, 0, 1, 0u32]),
280+
] {
281+
assert_eq!(
282+
Word::from(key0.map(Felt::from_u32)).cmp(&Word::from(key1.map(Felt::from_u32))),
283+
expected
284+
);
285+
}
286+
}

0 commit comments

Comments
 (0)