refactor: swap Goldilocks re-export to the Felt type unified for off-chain and on-chain code#819
refactor: swap Goldilocks re-export to the Felt type unified for off-chain and on-chain code#819
Goldilocks re-export to the Felt type unified for off-chain and on-chain code#819Conversation
Felt type unified for off-chain and on-chain codeGoldilocks re-export to the Felt type unified for off-chain and on-chain code
f38f76c to
bff706b
Compare
make `Word` type to have the WIT-compatible shape
bff706b to
ba5aa2b
Compare
huitseeker
left a comment
There was a problem hiding this comment.
Some concerns with unsafe conversions, and questions inline.
| /// | ||
| /// # Safety | ||
| /// This assumes `(Felt, Felt, Felt, Felt)` has the same memory layout as `[Felt; 4]`. | ||
| fn as_elements_array(&self) -> &[Felt; WORD_SIZE_FELT] { |
There was a problem hiding this comment.
The cast from &(Felt, Felt, Felt, Felt) to &[Felt; 4] indeed assumes identical memory layout, but Rust does not guarantee tuple layout. This could lead to UB if the compiler changes layout.
I'd suggest adding compile-time assertions like const_assert!(size_of::<(Felt, Felt, Felt, Felt)>() == size_of::<[Felt; 4]>()); at least (and a proptest) to verify the assumption of equivalence at build (/test) time.
| @@ -161,14 +208,13 @@ impl Word { | |||
| where | |||
| I: Iterator<Item = &'a Self>, | |||
| { | |||
| words.flat_map(|d| d.0.iter()) | |||
| words.flat_map(|d| d.as_elements().iter()) | |||
| } | |||
|
|
|||
| /// Returns all elements of multiple words as a slice. | |||
| pub fn words_as_elements(words: &[Self]) -> &[Felt] { | |||
There was a problem hiding this comment.
words_as_elements casts &[Word] to &[Felt] via raw pointer. With #[repr(C, align(16))] on Word, this assumes no padding between elements and specific field ordering. Suggest adding const_assert! checks for size and alignment, or using the safe words_as_elements_iter instead where performance allows.
| @@ -0,0 +1,20 @@ | |||
| //! A unified `Felt` for Miden Rust code. | |||
There was a problem hiding this comment.
The new miden-field crate has no tests. Since this is cryptographic code used throughout the codebase, consider adding unit tests for both the native and WASM implementations to ensure correctness of field operations.
There was a problem hiding this comment.
I'm not sure adding tests for miden target would be possible here since implementation of these methods would be delegated to compiler intrinsics. IIUC, this would require the whole compiler pipeline to be present. So, maybe a better places for these is in the compiler repo?
For native implementation, I think the main thing we'd be testing is that we didn't mix up delegated function calls. Maybe there is a way to add a couple of tests which would run all (most?) operations on some random elements and make sure we get the same result for Felt and native Goldilocks?
| // We cannot define this type as `Word([Felt;4])` since there is no struct tuple support | ||
| // and fixed array support is not complete in WIT. For the type remapping to work the | ||
| // bindings are expecting the remapped type to be the same shape as the one generated from | ||
| // WIT. |
There was a problem hiding this comment.
Would it not be possible to use:
#[repr(C)]
pub struct Word {
a: Felt,
b: Felt,
c: Felt,
d: Felt,
}from a WIT PoV, or do you need the tuple?
Because that is layout-compatible with [Felt; 4].
… `build.yml` To test building for the wasm32+miden (on-chain) target.
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! Not a full review yet, but I left some comments inline.
| [dev-dependencies] | ||
| rand = { default-features = false, version = "0.9" } | ||
| rstest = { version = "0.26" } |
| keywords.workspace = true | ||
| license.workspace = true | ||
| name = "miden-field" | ||
| readme = "../README.md" |
There was a problem hiding this comment.
I think we should have a dedicated README file for this crate explaining (briefly) the motivation for it and how it works.
| # dependendies for on-chain target | ||
| [dependencies] | ||
| thiserror = { default-features = false, version = "2.0" } |
There was a problem hiding this comment.
Not sure if the comment here is correct - I think these dependencies also apply for off chain targets.
I would also move these above line 18.
| /// A `Felt` backed by Plonky3's Goldilocks field element. | ||
| #[derive(Copy, Clone, Default, serde::Serialize, serde::Deserialize)] | ||
| #[repr(transparent)] | ||
| pub struct Felt(pub Goldilocks); |
There was a problem hiding this comment.
Does the inner value need to be pub?
| miden-crypto-derive.workspace = true | ||
| miden-field = { path = "../miden-field" } | ||
| miden-serde-utils.workspace = true |
There was a problem hiding this comment.
Could we not use miden-field.workspace = true here?
Also, since we now have more than a couple of workspace dependencies, could we move them into a separate section? Similar to how we do it here.
| pub fn rand_u64() -> u64 { | ||
| let mut bytes = [0u8; 8]; | ||
| let rng = &mut rand::rng(); | ||
| rng.fill(&mut bytes[..]); | ||
| let bytes = bytes[..8].try_into().unwrap(); | ||
| u64::from_le_bytes(bytes) | ||
| } |
There was a problem hiding this comment.
Could this not be just:
pub fn rand_u64() -> u64 {
rand::random()
}And if so, maybe we can just use rand::random() instead of this function directly?
There was a problem hiding this comment.
I did a very cursory review of this file.
| use core::fmt::Write; | ||
|
|
||
| let mut msg = String::new(); | ||
| write!(&mut msg, "value {value} is not a valid felt") | ||
| .expect("writing to string should not fail"); | ||
| miden_serde_utils::DeserializationError::InvalidValue(msg) |
There was a problem hiding this comment.
Why not just use format! here?
DeserializationError::InvalidValue(format!(
"value {value} is not a valid Goldilocks field element",
))Also, now that we have these, we can probably get rid of the direct implementations on Goldilocks (in miden-serde-utils/src/lib.rs). Though, this would be a breaking change and should be done in a follow-up PR.
| #[inline] | ||
| fn from_canonical_checked(int: i64) -> Option<Self> { | ||
| Goldilocks::from_canonical_checked(int).map(From::from) | ||
| } |
There was a problem hiding this comment.
Maybe not for this PR, but it would be nice to implement all these methods directly on Felt struct. This way, we won't have to import a bunch of traits every time we want to call one of such methods.
|
|
||
| const WORD_SIZE_FELT: usize = 4; | ||
| const WORD_SIZE_BYTES: usize = 32; | ||
| pub const WORD_SIZE_FELT: usize = 4; |
There was a problem hiding this comment.
nit: I'd rename this to WORD_SIZE_FELTS to be consistent with WORD_SIZE_BYTES.
Close #771
This PR swaps the
p3_goldilocks::Goldilocks as Feltre-export to the wrapperFelt(p3_goldilocks::Goldilocks)type for non-Miden targets. For the Miden targetFelt(f32)is compiled (see explanation in #771).Besides that
Wordtype is changed to have the WIT-compatible shape (structwith a named field).All
Feltmethods and implemented traits are delegating everything to the underlyingGoldilockstype and its trait implementations. So theFeltshould be 100% API equivalent to theGoldilockstype and can be used as a drop-in replacement. This allows us to release it as av0.22.xpatch version so that it can be included in VM v0.21 release. That's why I made this PR against themainbranch.The corresponding PR in the VM repo that updates VM to the version of
miden-cryptofrom this branch is 0xMiden/miden-vm#2649.The corresponding PR in the compiler repo migrating to the
miden-fieldcrate from this PR is 0xMiden/compiler#923TODO:
Wordshape to named fields (a, b, c, d);impl Arbitrary for Felt;