Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ba5aa2b
refactor: add `Felt` type unified for off-chain and on-chain code,
greenhat Jan 27, 2026
1519870
refactor: move `Word` and derived types from `miden-crypto` to `miden…
greenhat Feb 11, 2026
2340896
chore: add `make build_target_miden` task and `targe-miden` CI job in…
greenhat Feb 13, 2026
d852c32
fix: bring wasm+miden Felt API on par with native
greenhat Feb 13, 2026
06d007b
fix: build `miden-field` for wasm+miden target
greenhat Feb 13, 2026
c5ae258
fix: imports for `LexicographicWord`, remove `AsRef` impl for `Word`
greenhat Feb 16, 2026
0e06cae
refactor: change the shape of the `Word` type from a tuple to named f…
greenhat Feb 16, 2026
d0a0ae6
chore: formatting
greenhat Feb 16, 2026
2374a2b
refactor: make the inner type private in `Felt`
greenhat Feb 16, 2026
9ab51d9
chore: make `miden-field` crate a workspace dependency
greenhat Feb 16, 2026
2adaca4
chore: add README.md for the `miden-field` crate
greenhat Feb 16, 2026
d973a9b
refactor: use `format!`
greenhat Feb 16, 2026
b818ccc
fix: re-export `miden_field::word` module from `miden-crypto`
greenhat Feb 17, 2026
f3f7bda
refactor: rename `miden_field::WORD_SIZE_FELT` to `WORD_SIZE_FELTS`
greenhat Feb 17, 2026
81f9ce6
fix: `cargo check --all-targets --no-default-features` for the `miden…
greenhat Feb 17, 2026
a08abee
test: implement `Arbitrary` for `Felt`
greenhat Feb 17, 2026
ad0c03f
refactor: `native.rs` -> `native/mod.rs`
greenhat Feb 17, 2026
ea183ce
chore: add tests to ensure equivalence of `Felt` vs `Goldilocks`
greenhat Feb 18, 2026
2d67e4c
chore: convert `Word` tests to proptests
greenhat Feb 18, 2026
21199a3
refactor: re-export `p3-field` traits in `miden-field`
greenhat Feb 19, 2026
04f5af6
fix: gate `word!` to be available in the off-chain target only
greenhat Feb 19, 2026
a897b5b
test: add the roundtrip test for `Word::reversed`
greenhat Feb 19, 2026
daab99c
fix: don't clobber `RUSTFLAGS` env var `build-target-miden` task
greenhat Feb 19, 2026
f3e1a5a
chore: add doc comment for `Word::reversed`
greenhat Feb 19, 2026
440b59e
refactor: define `align(16)` for `Word` only for miden target
greenhat Feb 19, 2026
59dadc1
test: add a compile-time check that `Word` has the same layout as `[F…
greenhat Feb 20, 2026
7e5a2cb
fix: check that `MIDENC_TARGET_IS_MIDEN_VM` env var is not empty
greenhat Feb 20, 2026
12a70dc
chore: add comment explaining why `Word` fields have to be public
greenhat Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,21 @@ jobs:
rustup default ${{ matrix.toolchain }}
rustup target add wasm32-unknown-unknown
make build-no-std

target-miden:
name: Build miden-field for on-chain target
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly]
steps:
- uses: actions/checkout@main
- name: Cleanup large tools for build space
uses: ./.github/actions/cleanup-runner
- name: Build miden-field for on-chain target
run: |
rustup update --no-self-update ${{ matrix.toolchain }}
rustup default ${{ matrix.toolchain }}
rustup target add wasm32-wasip2
make build-target-miden
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.22.3 (unreleased)

- Refactored to introduce a unified `Felt` type for on-chain and off-chain code ([#819](https://github.com/0xMiden/crypto/pull/819)).

## 0.22.2 (2026-02-01)

- Re-exported `p3_keccak::VECTOR_LEN`.
Expand Down
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
exclude = ["miden-crypto-fuzz"]
members = ["miden-crypto", "miden-crypto-derive", "miden-serde-utils"]
members = ["miden-crypto", "miden-crypto-derive", "miden-field", "miden-serde-utils"]
resolver = "3"

[workspace.package]
Expand All @@ -15,6 +15,7 @@ version = "0.22.2"

[workspace.dependencies]
miden-crypto-derive = { path = "miden-crypto-derive", version = "0.22" }
miden-field = { path = "miden-field", version = "0.22" }
miden-serde-utils = { path = "miden-serde-utils", version = "0.22" }

[workspace.lints.rust]
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ build: ## Build with default features enabled
build-no-std: ## Build without the standard library
cargo build --release --no-default-features --target wasm32-unknown-unknown

.PHONY: build-target-miden
build-target-miden: ## Build `miden-field` for wasm32-wasip2 with `--cfg miden`
RUSTFLAGS="$${RUSTFLAGS:+$$RUSTFLAGS }--cfg miden" cargo build --release -p miden-field --target wasm32-wasip2

.PHONY: build-avx2
build-avx2: ## Build with avx2 support
RUSTFLAGS="-C target-feature=+avx2" cargo build --release
Expand Down
57 changes: 31 additions & 26 deletions miden-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,36 +86,40 @@ internal = ["concurrent"]
rocksdb = ["concurrent", "dep:rocksdb"]
serde = ["dep:serde", "serde?/alloc"]
std = ["blake3/std", "dep:cc", "miden-serde-utils/std", "rand/std", "rand/thread_rng"]
testing = ["dep:proptest"]
testing = ["dep:proptest", "miden-field/testing"]

[dependencies]
blake3 = { default-features = false, version = "1.8" }
chacha20poly1305 = { features = ["alloc", "stream"], version = "0.10" }
clap = { features = ["derive"], optional = true, version = "4.5" }
curve25519-dalek = { default-features = false, version = "4" }
ed25519-dalek = { features = ["zeroize"], version = "2" }
flume = { version = "0.11.1" }
hashbrown = { features = ["serde"], optional = true, version = "0.16" }
hkdf = { default-features = false, version = "0.12" }
k256 = { features = ["ecdh", "ecdsa"], version = "0.13" }
# Miden dependencies
miden-crypto-derive.workspace = true
miden-field.workspace = true
miden-serde-utils.workspace = true
num = { default-features = false, features = ["alloc", "libm"], version = "0.4" }
num-complex = { default-features = false, version = "0.4" }
proptest = { default-features = false, features = ["alloc"], optional = true, version = "1.7" }
rand = { default-features = false, version = "0.9" }
rand-utils = { optional = true, package = "winter-rand-utils", version = "0.13" }
rand_chacha = { default-features = false, version = "0.9" }
rand_core = { default-features = false, version = "0.9" }
rand_hc = { version = "0.3" }
rayon = { optional = true, version = "1.10" }
rocksdb = { default-features = false, features = ["bindgen-runtime", "lz4"], optional = true, version = "0.24" }
serde = { default-features = false, features = ["derive"], optional = true, version = "1.0" }
sha2 = { default-features = false, version = "0.10" }
sha3 = { default-features = false, version = "0.10" }
subtle = { default-features = false, version = "2.6" }
thiserror = { default-features = false, version = "2.0" }
x25519-dalek = { default-features = false, features = ["static_secrets"], version = "2.0" }

# External dependencies
blake3 = { default-features = false, version = "1.8" }
chacha20poly1305 = { features = ["alloc", "stream"], version = "0.10" }
clap = { features = ["derive"], optional = true, version = "4.5" }
curve25519-dalek = { default-features = false, version = "4" }
ed25519-dalek = { features = ["zeroize"], version = "2" }
flume = { version = "0.11.1" }
hashbrown = { features = ["serde"], optional = true, version = "0.16" }
hkdf = { default-features = false, version = "0.12" }
k256 = { features = ["ecdh", "ecdsa"], version = "0.13" }
num = { default-features = false, features = ["alloc", "libm"], version = "0.4" }
num-complex = { default-features = false, version = "0.4" }
proptest = { default-features = false, features = ["alloc"], optional = true, version = "1.7" }
rand = { default-features = false, version = "0.9" }
rand-utils = { optional = true, package = "winter-rand-utils", version = "0.13" }
rand_chacha = { default-features = false, version = "0.9" }
rand_core = { default-features = false, version = "0.9" }
rand_hc = { version = "0.3" }
rayon = { optional = true, version = "1.10" }
rocksdb = { default-features = false, features = ["bindgen-runtime", "lz4"], optional = true, version = "0.24" }
serde = { default-features = false, features = ["derive"], optional = true, version = "1.0" }
sha2 = { default-features = false, version = "0.10" }
sha3 = { default-features = false, version = "0.10" }
subtle = { default-features = false, version = "2.6" }
thiserror = { default-features = false, version = "2.0" }
x25519-dalek = { default-features = false, features = ["static_secrets"], version = "2.0" }

# Upstream Plonky3 dependencies
p3-air = { default-features = false, version = "0.4.2" }
Expand All @@ -142,6 +146,7 @@ assert_matches = { default-features = false, version = "1.5" }
criterion = { features = ["html_reports"], version = "0.7" }
hex = { default-features = false, features = ["alloc"], version = "0.4" }
itertools = { version = "0.14" }
miden-field = { features = ["testing"], workspace = true }
proptest = { default-features = false, features = ["alloc"], version = "1.7" }
rand-utils = { package = "winter-rand-utils", version = "0.13" }
rstest = { version = "0.26" }
Expand Down
2 changes: 1 addition & 1 deletion miden-crypto/benches/word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

use criterion::{Criterion, criterion_group, criterion_main};
// Import Word modules
use miden_crypto::{Felt, Word, word::LexicographicWord};
use miden_crypto::{Felt, LexicographicWord, Word};

// Import common utilities
mod common;
Expand Down
3 changes: 1 addition & 2 deletions miden-crypto/src/hash/blake/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
use alloc::vec::Vec;

use p3_field::PrimeField64;
use p3_goldilocks::Goldilocks as Felt;
use proptest::prelude::*;

use super::*;
use crate::rand::test_utils::rand_vector;
use crate::{Felt, rand::test_utils::rand_vector};

#[test]
fn blake3_hash_elements() {
Expand Down
15 changes: 7 additions & 8 deletions miden-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@ pub mod ies;
pub mod merkle;
pub mod rand;
pub mod utils;
pub mod word;

// RE-EXPORTS
// ================================================================================================
pub use p3_goldilocks::Goldilocks as Felt;
pub use word::{Word, WordError};
pub use miden_field::{Felt, LexicographicWord, Word, WordError, word};

pub mod field {
//! Traits and utilities for working with the Goldilocks finite field (i.e.,
//! [Felt](super::Felt)).

pub use p3_field::{
BasedVectorSpace, ExtensionField, Field, PrimeCharacteristicRing, PrimeField64,
TwoAdicField, batch_multiplicative_inverse, extension::BinomialExtensionField,
integers::QuotientMap,
pub use miden_field::{
BasedVectorSpace, BinomialExtensionField, BinomiallyExtendable,
BinomiallyExtendableAlgebra, ExtensionField, Field, HasTwoAdicBinomialExtension,
InjectiveMonomial, Packable, PermutationMonomial, PrimeCharacteristicRing, PrimeField,
PrimeField64, QuotientMap, RawDataSerializable, TwoAdicField, batch_multiplicative_inverse,
};

pub use super::batch_inversion::batch_inversion_allow_zeros;
Expand Down Expand Up @@ -140,7 +139,7 @@ pub type Set<V> = alloc::collections::BTreeSet<V>;
// ================================================================================================

/// Number of field elements in a word.
pub const WORD_SIZE: usize = 4;
pub const WORD_SIZE: usize = word::WORD_SIZE_FELTS;

/// Field element representing ZERO in the Miden base filed.
pub const ZERO: Felt = Felt::ZERO;
Expand Down
14 changes: 14 additions & 0 deletions miden-crypto/src/rand/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Pseudo-random element generation.

use miden_field::word::WORD_SIZE_BYTES;
use p3_field::PrimeField64;
use rand::RngCore;

Expand Down Expand Up @@ -104,6 +105,19 @@ impl Randomizable for Felt {
}
}

impl Randomizable for Word {
const VALUE_SIZE: usize = WORD_SIZE_BYTES;

fn from_random_bytes(bytes: &[u8]) -> Option<Self> {
let bytes_array: Option<[u8; 32]> = bytes.try_into().ok();
if let Some(bytes_array) = bytes_array {
Self::try_from(bytes_array).ok()
} else {
None
}
}
}

impl<const N: usize> Randomizable for [u8; N] {
const VALUE_SIZE: usize = N;

Expand Down
55 changes: 1 addition & 54 deletions miden-crypto/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub use miden_serde_utils::{
};
use p3_field::{PrimeCharacteristicRing, RawDataSerializable, integers::QuotientMap};
use p3_maybe_rayon::prelude::*;
use thiserror::Error;

use crate::{Felt, Word, field::PrimeField64};

Expand Down Expand Up @@ -41,59 +40,7 @@ pub fn word_to_hex(w: &Word) -> Result<String, fmt::Error> {
Ok(s)
}

/// Renders an array of bytes as hex into a String.
pub fn bytes_to_hex_string<const N: usize>(data: [u8; N]) -> String {
let mut s = String::with_capacity(N + 2);

s.push_str("0x");
for byte in data.iter() {
write!(s, "{byte:02x}").expect("formatting hex failed");
}

s
}

/// Defines errors which can occur during parsing of hexadecimal strings.
#[derive(Debug, Error)]
pub enum HexParseError {
#[error("expected hex data to have length {expected}, including the 0x prefix, found {actual}")]
InvalidLength { expected: usize, actual: usize },
#[error("hex encoded data must start with 0x prefix")]
MissingPrefix,
#[error("hex encoded data must contain only characters [0-9a-fA-F]")]
InvalidChar,
#[error("hex encoded values of a Digest must be inside the field modulus")]
OutOfRange,
}

/// Parses a hex string into an array of bytes of known size.
pub fn hex_to_bytes<const N: usize>(value: &str) -> Result<[u8; N], HexParseError> {
let expected: usize = (N * 2) + 2;
if value.len() != expected {
return Err(HexParseError::InvalidLength { expected, actual: value.len() });
}

if !value.starts_with("0x") {
return Err(HexParseError::MissingPrefix);
}

let mut data = value.bytes().skip(2).map(|v| match v {
b'0'..=b'9' => Ok(v - b'0'),
b'a'..=b'f' => Ok(v - b'a' + 10),
b'A'..=b'F' => Ok(v - b'A' + 10),
_ => Err(HexParseError::InvalidChar),
});

let mut decoded = [0u8; N];
for byte in decoded.iter_mut() {
// These `unwrap` calls are okay because the length was checked above
let high: u8 = data.next().unwrap()?;
let low: u8 = data.next().unwrap()?;
*byte = (high << 4) + low;
}

Ok(decoded)
}
pub use miden_field::utils::{HexParseError, bytes_to_hex_string, hex_to_bytes};

// CONVERSIONS BETWEEN BYTES AND ELEMENTS
// ================================================================================================
Expand Down
Loading