Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
- Validated and retained `DebugInfo` when reading untrusted packages, with bounded decoding for hostile data and an explicit unmetered decoder for analysis ([#3460](https://github.com/0xMiden/miden-vm/pull/3460)).

#### Fixes
- Added `dup u32lt.64 assert.err` boundary checks to `u64::shl`, `u64::shr`, `u64::rotl`, and `u64::rotr` and updated cycle counts ([#3368](https://github.com/0xMiden/miden-vm/pull/3368), fixes [#3360](https://github.com/0xMiden/miden-vm/issues/3360)).

- Fixed deferred MSM session lowering so structurally distinct claims with the same canonical
expression retain their own hashes, while exact repeats reuse one balanced eval row
Expand Down Expand Up @@ -159,6 +160,7 @@
## v0.29.0 (2026-08-04)

#### Changes
- `FastProcessor` `restore_call_state()` and `restore_context()` now return `OperationError::Internal` instead of panicking on empty stacks ([#3371](https://github.com/0xMiden/miden-vm/pull/3371), fixes [#3296](https://github.com/0xMiden/miden-vm/issues/3296)).

- [BREAKING] Recursive MASM verification now accepts a claim commitment and authenticates the advice-supplied claim and kernel witness. Rust callers construct request-addressed inputs with `RecursiveVerifierInputs::for_request` ([#3447](https://github.com/0xMiden/miden-vm/pull/3447)).

Expand Down Expand Up @@ -196,7 +198,6 @@
- [BREAKING] Removed the `miden::core::crypto::hashes::sha512` MASM module, Rust handler, docs, and tests. SHA-512 support is temporarily removed from core-lib and will be reintroduced once it is supported by the precompiles prover ([#3222](https://github.com/0xMiden/miden-vm/pull/3222)).
- [BREAKING] Changed the `miden::core::crypto::dsa::ecdsa_k256_keccak` advice/signature ABI to `QX[8] || QY[8] || SIG_R[8] || SIG_S[8]` as little-endian u32 field elements. Existing 65-byte signature advice must be re-encoded as `(r, s)` limbs without a recovery byte ([#3222](https://github.com/0xMiden/miden-vm/pull/3222)).
- [BREAKING] Migrated proof-bound precompiles to the deferred-DAG proof wire. `ExecutionProof` now carries a `DeferredStateWire`, proof serialization is incompatible with previous proof-bound precompile requests, and verification rehydrates the wire under the built-in `miden_precompiles::registry()` before binding the resulting deferred root to the STARK public inputs ([#3222](https://github.com/0xMiden/miden-vm/pull/3222)).
- `FastProcessor` `restore_call_state()` and `restore_context()` now return `OperationError::Internal` instead of panicking on empty stacks ([#3371](https://github.com/0xMiden/miden-vm/pull/3371), fixes [#3296](https://github.com/0xMiden/miden-vm/issues/3296)).
- Bound deferred precompile STARK proofs to the generated precompile ACE relation digest ([#3344](https://github.com/0xMiden/miden-vm/pull/3344)).
- [BREAKING] Split Poseidon2 permutation rows out of `ChipletsAir` into `Poseidon2PermutationAir`, and updated the recursive verifier ACE registry for three AIRs ([#3345](https://github.com/0xMiden/miden-vm/pull/3345)).
- [BREAKING] Optimize periodic columns evaluation for fewer ACE gates ([#3347](https://github.com/0xMiden/miden-vm/pull/3347)).
Expand Down
19 changes: 15 additions & 4 deletions crates/lib/core/asm/math/u64.masm
Original file line number Diff line number Diff line change
Expand Up @@ -576,8 +576,10 @@ end
#! The shift value n should be in the range [0, 64), otherwise it will result in an error.
#! Stack transition looks as follows:
#! [n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = (a << n) mod 2^64.
#! This takes 21 cycles.
#! This takes 27 cycles.
pub proc shl(n: u32, a: u64) -> u64
dup u32lt.64 assert.err="shift amount must be in the range [0, 64)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds work before the old body, so the cycle-count docs above this and the other three changed procs now look stale. Could you recompute and update the This takes ... cycles comments for shl, shr, rotl, and rotr?

One way to check them is to add a temporary core-lib test that wraps each exec.u64::* call with clk before and after the call, then computes the delta. Since clk itself costs one cycle, subtract the first clk instruction from the measured difference.


pow2 # [2^n, a_lo, a_hi]
u32split # [pow_lo, pow_hi, a_lo, a_hi]
movup.2 # [a_lo, pow_lo, pow_hi, a_hi]
Expand All @@ -591,8 +593,11 @@ end
#! The shift value n should be in the range [0, 64), otherwise it will result in an error.
#! Stack transition looks as follows:
#! [n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a >> n.
#! This takes 60 or 61 cycles, depending on n.
#! This takes 66 or 67 cycles, depending on n.
pub proc shr(n: u32, a: u64) -> u64
# validate shift amount is in [0, 64)
dup u32lt.64 assert.err="shift amount must be in the range [0, 64)"

# ==========================================================================
# RIGHT SHIFT: Computes a >> n where a is 64-bit and n is shift amount
# Input: [n, a_lo, a_hi, ...] where a = a_hi * 2^32 + a_lo
Expand Down Expand Up @@ -735,8 +740,11 @@ end
#! The rotation amount n should be in the range [0, 64), otherwise it will result in an error.
#! Stack transition looks as follows:
#! [n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a <<< n (rotate left).
#! This takes 46 cycles.
#! This takes 52 cycles.
pub proc rotl(n: u32, a: u64) -> u64
# validate rotation amount is in [0, 64)
dup u32lt.64 assert.err="shift amount must be in the range [0, 64)"

# ==========================================================================
# LEFT ROTATION: Computes a <<< n (rotate left by n bits)
# Input: [n, a_lo, a_hi, ...] where a = a_hi * 2^32 + a_lo
Expand Down Expand Up @@ -827,8 +835,11 @@ end
#! The rotation amount n should be in the range [0, 64), otherwise it will result in an error.
#! Stack transition looks as follows:
#! [n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a >>> n (rotate right).
#! This takes 60 cycles.
#! This takes 66 cycles.
pub proc rotr(n: u32, a: u64) -> u64
# validate rotation amount is in [0, 64)
dup u32lt.64 assert.err="shift amount must be in the range [0, 64)"

# ==========================================================================
# RIGHT ROTATION: Computes a >>> n (rotate right by n bits)
# Input: [n, a_lo, a_hi, ...] where a = a_hi * 2^32 + a_lo
Expand Down
8 changes: 4 additions & 4 deletions crates/lib/core/docs/math/u64.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
| or | Performs bitwise OR of two unsigned 64 bit integers.<br />The input values are assumed to be represented using 32 bit limbs, but this is not checked.<br />Stack transition looks as follows:<br />[b_lo, b_hi, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a OR b.<br />This takes 5 cycles.<br /> |
| xor | Performs bitwise XOR of two unsigned 64 bit integers.<br />The input values are assumed to be represented using 32 bit limbs, but this is not checked.<br />Stack transition looks as follows:<br />[b_lo, b_hi, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a XOR b.<br />This takes 5 cycles.<br /> |
| not | Performs bitwise NOT of one unsigned 64 bit integer.<br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br />Stack transition looks as follows:<br />[a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = !a (i.e., 2^64 - 1 - a).<br />This takes 4 cycles.<br /> |
| shl | Performs left shift of one unsigned 64-bit integer.<br />The input value to be shifted is assumed to be represented using 32 bit limbs, but this is not checked.<br />The shift value n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = (a << n) mod 2^64.<br />This takes 21 cycles.<br /> |
| shr | Performs right shift of one unsigned 64-bit integer.<br />The input value to be shifted is assumed to be represented using 32 bit limbs, but this is not checked.<br />The shift value n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a >> n.<br />This takes 60 or 61 cycles, depending on n.<br /> |
| rotl | Performs left rotation of one unsigned 64-bit integer.<br />The input value to be rotated is assumed to be represented using 32 bit limbs, but this is not checked.<br />The rotation amount n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a <<< n (rotate left).<br />This takes 46 cycles.<br /> |
| rotr | Performs right rotation of one unsigned 64-bit integer.<br />The input value to be rotated is assumed to be represented using 32 bit limbs, but this is not checked.<br />The rotation amount n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a >>> n (rotate right).<br />This takes 60 cycles.<br /> |
| shl | Performs left shift of one unsigned 64-bit integer.<br />The input value to be shifted is assumed to be represented using 32 bit limbs, but this is not checked.<br />The shift value n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = (a << n) mod 2^64.<br />This takes 27 cycles.<br /> |
| shr | Performs right shift of one unsigned 64-bit integer.<br />The input value to be shifted is assumed to be represented using 32 bit limbs, but this is not checked.<br />The shift value n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a >> n.<br />This takes 66 or 67 cycles, depending on n.<br /> |
| rotl | Performs left rotation of one unsigned 64-bit integer.<br />The input value to be rotated is assumed to be represented using 32 bit limbs, but this is not checked.<br />The rotation amount n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a <<< n (rotate left).<br />This takes 52 cycles.<br /> |
| rotr | Performs right rotation of one unsigned 64-bit integer.<br />The input value to be rotated is assumed to be represented using 32 bit limbs, but this is not checked.<br />The rotation amount n should be in the range [0, 64), otherwise it will result in an error.<br />Stack transition looks as follows:<br />[n, a_lo, a_hi, ...] -> [c_lo, c_hi, ...], where c = a >>> n (rotate right).<br />This takes 66 cycles.<br /> |
| clz | Counts the number of leading zeros of one unsigned 64-bit integer.<br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br />Stack transition looks as follows:<br />[n_lo, n_hi, ...] -> [clz, ...], where clz is the number of leading zeros of value n.<br />This takes 48 cycles.<br /> |
| ctz | Counts the number of trailing zeros of one unsigned 64-bit integer.<br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br />Stack transition looks as follows:<br />[n_lo, n_hi, ...] -> [ctz, ...], where ctz is the number of trailing zeros of value n.<br />This takes 41 cycles.<br /> |
| clo | Counts the number of leading ones of one unsigned 64-bit integer.<br />The input value is assumed to be represented using 32 bit limbs, but this is not checked.<br />Stack transition looks as follows:<br />[n_lo, n_hi, ...] -> [clo, ...], where clo is the number of leading ones of value n.<br />This takes 47 cycles.<br /> |
Expand Down
118 changes: 118 additions & 0 deletions crates/lib/core/tests/math/u64_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1814,3 +1814,121 @@ fn split_u128(value: u128) -> (u64, u64, u64, u64) {
value as u32 as u64,
)
}

/// Test that shr with n >= 64 produces an assertion error.
#[test]
fn shr_out_of_range_errors() {
let a: u64 = 0x12345678_9abcdef0u64;
let (a1, a0) = split_u64(a);
let source = "
use miden::core::math::u64
begin
exec.u64::shr
end
";
let test = build_test!(source, &[64u64, a0, a1]);
expect_assert_error_message!(test);
let test = build_test!(source, &[100u64, a0, a1]);
expect_assert_error_message!(test);
}

/// Test that shl with n >= 64 produces an assertion error.
#[test]
fn shl_out_of_range_errors() {
let a: u64 = 0x12345678_9abcdef0u64;
let (a1, a0) = split_u64(a);
let source = "
use miden::core::math::u64
begin
exec.u64::shl
end
";
let test = build_test!(source, &[64u64, a0, a1]);
expect_assert_error_message!(test);
let test = build_test!(source, &[100u64, a0, a1]);
expect_assert_error_message!(test);
}

/// Test that rotl with n >= 64 produces an assertion error.
#[test]
fn rotl_out_of_range_errors() {
let a: u64 = 0x12345678_9abcdef0u64;
let (a1, a0) = split_u64(a);
let source = "
use miden::core::math::u64
begin
exec.u64::rotl
end
";
let test = build_test!(source, &[64u64, a0, a1]);
expect_assert_error_message!(test);
let test = build_test!(source, &[100u64, a0, a1]);
expect_assert_error_message!(test);
}

/// Test that rotr with n >= 64 produces an assertion error.
#[test]
fn rotr_out_of_range_errors() {
let a: u64 = 0x12345678_9abcdef0u64;
let (a1, a0) = split_u64(a);
let source = "
use miden::core::math::u64
begin
exec.u64::rotr
end
";
let test = build_test!(source, &[64u64, a0, a1]);
expect_assert_error_message!(test);
let test = build_test!(source, &[100u64, a0, a1]);
expect_assert_error_message!(test);
}

// ============================================================================
// Cycle count baseline tests for shift/rotate operations
//
// These tests measure the cycle count of each procedure using the `clk`
// instruction and assert against known baselines. If the cycle count changes
// (e.g. due to implementation changes), the test fails and the expected
// value must be updated.
//
// The measurement stores the first `clk` value in memory (address 1000) to
// keep the operand stack layout clean for the procedure call. The measured
// delta includes the `exec` call overhead and memory instrumentation, which
// is consistent across runs for the same procedure signature.
// ============================================================================

#[test]
fn shift_rotate_cycle_baselines() {
let a: u64 = 0x12345678_9abcdef0u64;
let (a1, a0) = split_u64(a);

let mut mismatches = Vec::new();

for (name, n, proc_name, expected) in [
("shl_n5", 5u64, "shl", 51u64),
("shr_n5", 5u64, "shr", 67u64),
("shr_n33", 33u64, "shr", 67u64),
("rotl_n5", 5u64, "rotl", 51u64),
("rotr_n5", 5u64, "rotr", 67u64),
] {
let source = format!(
"
use miden::core::math::u64
begin
clk push.1000 mem_store
exec.u64::{proc_name}
clk push.1000 mem_load sub
swap drop swap drop
end
"
);
let test = build_test!(&source, &[n, a0, a1]);
let result = test.get_last_stack_state();
let cycles = result.iter().next().unwrap().as_canonical_u64();
if cycles != expected {
mismatches.push(format!("{name}: expected {expected}, got {cycles}"));
}
}

assert!(mismatches.is_empty(), "cycle count changed:\n{}", mismatches.join("\n"));
}
Loading