Skip to content

Commit 6af6c7d

Browse files
authored
Merge branch 'main' into next
2 parents ce2f33a + df8ef42 commit 6af6c7d

File tree

7 files changed

+91
-49
lines changed

7 files changed

+91
-49
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
- [BREAKING] Added validation to `PartialMmr::from_parts()` and `Deserializable` implementation, added `from_parts_unchecked()` for performance-critical code ([#812](https://github.com/0xMiden/crypto/pull/812)).
1414
- Added `Signature::from_der()` for ECDSA signatures over secp256k1 ([#842](https://github.com/0xMiden/crypto/pull/842)).
1515

16-
## 0.22.3 (unreleased)
16+
## 0.22.3 (2026-02-23)
1717

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

2022
## 0.22.2 (2026-02-01)
2123

miden-field/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub use wasm_miden::Felt;
1717
#[cfg(not(all(target_family = "wasm", miden)))]
1818
mod native;
1919
#[cfg(not(all(target_family = "wasm", miden)))]
20-
pub use native::Felt;
20+
pub use native::{Felt, FeltFromIntError};
2121

2222
pub mod utils;
2323

miden-field/src/native/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,43 @@ impl PermutationMonomial<7> for Felt {
357357
// CONVERSIONS
358358
// ================================================================================================
359359

360+
impl From<u8> for Felt {
361+
fn from(int: u8) -> Self {
362+
Self::from_u8(int)
363+
}
364+
}
365+
366+
impl From<u16> for Felt {
367+
fn from(int: u16) -> Self {
368+
Self::from_u16(int)
369+
}
370+
}
371+
372+
impl From<u32> for Felt {
373+
fn from(int: u32) -> Self {
374+
Self::from_u32(int)
375+
}
376+
}
377+
378+
impl TryFrom<u64> for Felt {
379+
type Error = FeltFromIntError;
380+
381+
fn try_from(int: u64) -> Result<Felt, Self::Error> {
382+
Felt::from_canonical_checked(int).ok_or(FeltFromIntError(int))
383+
}
384+
}
385+
386+
#[derive(Debug, thiserror::Error)]
387+
#[error("integer {0} is equal to or exceeds the felt modulus {modulus}", modulus = Felt::ORDER)]
388+
pub struct FeltFromIntError(u64);
389+
390+
impl FeltFromIntError {
391+
/// Returns the integer for which the conversion failed.
392+
pub fn as_u64(&self) -> u64 {
393+
self.0
394+
}
395+
}
396+
360397
impl Deref for Felt {
361398
type Target = Goldilocks;
362399

miden-field/src/native/tests.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,10 @@ fn felt_from_prime_subfield_is_transparent() {
293293
let f = Felt::from_prime_subfield(g);
294294
assert_eq!(f, g);
295295
}
296+
297+
/// Ensures TryFrom<u64> fails for inputs at or exceeding the modulus.
298+
#[test]
299+
fn felt_try_from_u64_fails_on_large_inputs() {
300+
Felt::try_from(u64::MAX).unwrap_err();
301+
Felt::try_from(Felt::ORDER).unwrap_err();
302+
}

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)