Skip to content

Commit 0870bf1

Browse files
committed
feat: Re-interpret xfe-slices as bfe-slices
In particular, the new function `as_flat_slice` transforms a reference to a slice of `XFieldElement`s into a reference to a slice of `BFieldElement`s, _without_ allocating any memory. This is particularly useful when hashing a slice of `XFieldElement`s in a performance-critical scenario.
1 parent 1aeb2a2 commit 0870bf1

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

twenty-first/src/math/b_field_element.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const PRIMITIVE_ROOTS: phf::Map<u64, u64> = phf_map! {
8080
/// In Montgomery representation. This implementation follows <https://eprint.iacr.org/2022/274.pdf>
8181
/// and <https://github.com/novifinancial/winterfell/pull/101/files>.
8282
#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq)]
83+
#[repr(transparent)]
8384
pub struct BFieldElement(u64);
8485

8586
/// Simplifies constructing [base field element][BFieldElement]s.

twenty-first/src/math/x_field_element.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub const EXTENSION_DEGREE: usize = 3;
3838
#[derive(
3939
Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize, BFieldCodec, Arbitrary,
4040
)]
41+
#[repr(transparent)]
4142
pub struct XFieldElement {
4243
pub coefficients: [BFieldElement; EXTENSION_DEGREE],
4344
}
@@ -177,6 +178,80 @@ macro_rules! xfe_array {
177178
};
178179
}
179180

181+
/// Re-interpret a slice of [`XFieldElement`]s as a slice of [`BFieldElement`]s
182+
/// without any memory allocation.
183+
///
184+
/// This function is semantically similar to [flat-mapping] the coefficients of
185+
/// the `XFieldElement`s (see examples). However, this function does not perform
186+
/// any memory allocation, which makes is particularly useful in
187+
/// high-performance scenarios.
188+
///
189+
/// # Examples
190+
///
191+
/// Re-interpretation behaves like flattening, but does not allocate or copy any
192+
/// data.
193+
///
194+
/// ```
195+
/// # use twenty_first::prelude::*;
196+
/// # use twenty_first::math::x_field_element::as_flat_slice;
197+
/// let xfes = xfe_vec![[17, 18, 19], [42, 42, 44], [97, 98, 99]];
198+
/// let bfes = bfe_vec![17, 18, 19, 42, 42, 44, 97, 98, 99];
199+
/// assert_eq!(&bfes, as_flat_slice(&xfes));
200+
/// ```
201+
///
202+
/// This can be particularly useful for hashing sequences of [`XFieldElement]`s,
203+
/// where ownership is irrelevant:
204+
///
205+
/// ```
206+
/// # use twenty_first::prelude::*;
207+
/// # use twenty_first::math::x_field_element::as_flat_slice;
208+
/// let xfes = xfe_vec![42; 17];
209+
/// let xfe_digest = Tip5::hash_varlen(as_flat_slice(&xfes));
210+
///
211+
/// // alternative requires copying data
212+
/// let bfes = xfes.into_iter().flat_map(|xfe| xfe.coefficients).collect::<Vec<_>>();
213+
/// let bfe_digest = Tip5::hash_varlen(&bfes);
214+
///
215+
/// assert_eq!(bfe_digest, xfe_digest);
216+
/// ```
217+
///
218+
/// [hashing]: crate::tip5::Tip5::hash_varlen
219+
/// [Tip5]: crate::tip5::Tip5
220+
/// [flat-mapping]: Iterator::flat_map
221+
pub fn as_flat_slice(xfe_slice: &[XFieldElement]) -> &[BFieldElement] {
222+
let slice_pointer = xfe_slice.as_ptr() as *const BFieldElement;
223+
let bfe_slice_len = xfe_slice.len() * EXTENSION_DEGREE;
224+
225+
// SAFETY:
226+
// - The slice_pointer is non-null, and is valid for reads for
227+
// xfe_slice.len() * size_of::<XFieldElement>() ==
228+
// xfe_slice.len() * size_of::<BFieldElement>() * EXTENSION_DEGREE
229+
// many bytes, and is properly aligned because both BFieldElement and
230+
// XFieldElement are #[repr(transparent)]. In particular:
231+
// - The entire memory range of the slice is contained within a single
232+
// allocated object. This is because of
233+
// (a) the origin of `slice_pointer` being a slice, and
234+
// (b) the layout and ABI of XFieldElement is identical to
235+
// [BFieldElement; EXTENSION_DEGREE] because of
236+
// #[repr(transparent)]
237+
// - The slice_pointer is non-null and aligned, again because of
238+
// #[repr(transparent)] on BFieldElement and XFieldElement.
239+
// - The slice_pointer points to xfe_slice.len() * EXTENSION_DEGREE
240+
// consecutive properly initialized values of type BFieldElement,
241+
// again because of #[repr(transparent)] on BFieldElement and
242+
// XFieldElement.
243+
// - The memory referenced by the returned slice cannot be mutated for
244+
// the duration of the lifetime of xfe_slice thanks to rust's
245+
// “mut XOR shared” compile time guarantees.
246+
// - The total size of the produced slice is no larger than isize::MAX
247+
// since it is identical to the total size of the initial size, and
248+
// adding that size to the slice_pointer does not “wrap around” the
249+
// address space because both, the slice_pointer and the total size
250+
// have been obtained through safe code or unsafe code for which the
251+
// safety invariants have been upheld.
252+
unsafe { std::slice::from_raw_parts(slice_pointer, bfe_slice_len) }
253+
}
254+
180255
impl From<XFieldElement> for Digest {
181256
/// Interpret the `XFieldElement` as a [`Digest`]. No hashing is performed.
182257
/// This interpretation can be useful for [`Tip5`](crate::prelude::Tip5)

0 commit comments

Comments
 (0)