Add decomposed Blake gates (BlakeG, TripleXor, M31ToU32) with packed-…#337
Add decomposed Blake gates (BlakeG, TripleXor, M31ToU32) with packed-…#337
Conversation
0b46154 to
9eb4d21
Compare
…limbs representation State words use packed u16 limbs in a single QM31: (low, high, 0, 0). All wire values are canonical M31. Input extraction uses Simd::unpack_idx. Introduces U32Wrapper<T> type for type-safe u32 packed-limbs wires. BLAKE2S_IV moved from circuit_air to circuits::blake (single source of truth). Includes blake_from_gates() and comparison test against monolithic blake. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9eb4d21 to
e04f49f
Compare
Stavbe
left a comment
There was a problem hiding this comment.
@Stavbe reviewed 2 files and made 36 comments.
Reviewable status: 2 of 8 files reviewed, 36 unresolved discussions (waiting on alon-f, Gali-StarkWare, gilbens-starkware, and ilyalesokhin-starkware).
crates/circuits/src/blake.rs line 133 at r1 (raw file):
} // --- Constants for Blake2s ---
remove
Code quote:
// --- Constants for Blake2s ---crates/circuits/src/blake.rs line 164 at r1 (raw file):
]; // --- Helpers ---
remove
Code quote:
// --- Helpers ---crates/circuits/src/blake.rs line 171 at r1 (raw file):
} /// Reconstructs a u32 from a packed-limbs QM31: `coord0 + coord1 * 65536`.
(limb_low, limb_high, 0, 0)
Code quote:
`coord0 + coord1 * 65536`.crates/circuits/src/blake.rs line 173 at r1 (raw file):
/// Reconstructs a u32 from a packed-limbs QM31: `coord0 + coord1 * 65536`. pub fn unpack_u32(v: QM31) -> u32 { v.0.0.0 + v.0.1.0 * 65536
Replace the .0.0.0 it looks weird, don't we have getter for that?. Also please write the constant as 2^(
Code quote:
v.0.0.0 + v.0.1.0 * 65536crates/circuits/src/blake.rs line 177 at r1 (raw file):
/// Wraps a single M31 value as a QM31: `(value, 0, 0, 0)`. pub fn u32_to_qm31(v: u32) -> QM31 {
m31
Code quote:
u32crates/circuits/src/blake.rs line 181 at r1 (raw file):
} // --- Builder functions ---
please remove
Code quote:
// --- Builder functions ---crates/circuits/src/blake.rs line 195 at r1 (raw file):
m1: U32Var, ) -> (U32Var, U32Var, U32Var, U32Var) { let mut a_val = unpack_u32(ctx.get(*a.get()));
Why not updating the input for the helper function to be U32VAR
crates/circuits/src/blake.rs line 202 at r1 (raw file):
let m1_val = unpack_u32(ctx.get(*m1.get())); a_val = a_val.wrapping_add(b_val).wrapping_add(m0_val);
it this mode P?
Code quote:
.wrapping_addcrates/circuits/src/blake.rs line 269 at r1 (raw file):
/// Computes Blake2s hash using decomposed gates (BlakeG, TripleXor, M31ToU32) instead of the /// monolithic Blake gate. Produces the same output as [`blake`].
remove
and add all the comments that the other blake have
Code quote:
/// Computes Blake2s hash using decomposed gates (BlakeG, TripleXor, M31ToU32) instead of the
/// monolithic Blake gate. Produces the same output as [`blake`].crates/circuits/src/blake.rs line 274 at r1 (raw file):
// 1. Extract individual M31 message words from input QM31 vars, then convert each to U32Wrapper // packed limbs for blake_g_gate.
// Unpack each QM31 containing the messgae into U32Vars
Code quote:
// 1. Extract individual M31 message words from input QM31 vars, then convert each to U32Wrapper
// packed limbs for blake_g_gate.crates/circuits/src/blake.rs line 277 at r1 (raw file):
let mut message_u32s: Vec<U32Var> = Vec::new(); for &var in input { let simd = Simd::from_packed(vec![var], 4);
Maybe we can do even more then 4 together
crates/circuits/src/blake.rs line 284 at r1 (raw file):
} // 2. Pad message to complete 64-byte (16 u32-word) blocks.
remove the 2.
crates/circuits/src/blake.rs line 285 at r1 (raw file):
// 2. Pad message to complete 64-byte (16 u32-word) blocks. let n_blocks = std::cmp::max(1, n_bytes.div_ceil(64));
const
Code quote:
64crates/circuits/src/blake.rs line 286 at r1 (raw file):
// 2. Pad message to complete 64-byte (16 u32-word) blocks. let n_blocks = std::cmp::max(1, n_bytes.div_ceil(64)); let total_words = n_blocks * 16;
const
Code quote:
16crates/circuits/src/blake.rs line 293 at r1 (raw file):
// 3. Initialize chaining value: IV with parameter block XOR, as packed-limbs constants. All // limb values are < 2^16 < P, so ctx.constant() is safe.
Initlialize 'h'
Code quote:
// 3. Initialize chaining value: IV with parameter block XOR, as packed-limbs constants. All
// limb values are < 2^16 < P, so ctx.constant() is safe.crates/circuits/src/blake.rs line 295 at r1 (raw file):
// limb values are < 2^16 < P, so ctx.constant() is safe. let mut h: [U32Var; 8] = std::array::from_fn(|i| { let iv_val = if i == 0 { BLAKE2S_IV[0] ^ 0x01010020 } else { BLAKE2S_IV[i] };
Document: (config: no key, 32 bytes output).
crates/circuits/src/blake.rs line 299 at r1 (raw file):
}); // 4. Compress each block.
remove the 4:
crates/circuits/src/blake.rs line 302 at r1 (raw file):
for block_idx in 0..n_blocks { let block: [U32Var; 16] = std::array::from_fn(|i| message_u32s[block_idx * 16 + i]); let t = std::cmp::min(n_bytes, (block_idx + 1) * 64) as u64;
Document
crates/circuits/src/blake.rs line 305 at r1 (raw file):
let last = block_idx == n_blocks - 1; let old_h = h;
current_h. it is not ols
crates/circuits/src/blake.rs line 308 at r1 (raw file):
// Set up working vector: v[0..8] = h, v[8..16] = IV with counter/flag pre-XORed. let t_low = (t & 0xFFFFFFFF) as u32;
t can be u32 from the beginning
crates/circuits/src/blake.rs line 325 at r1 (raw file):
// 10 rounds of mixing. for s in &BLAKE_SIGMA {
current_petmutation in &
crates/circuits/src/blake.rs line 326 at r1 (raw file):
// 10 rounds of mixing. for s in &BLAKE_SIGMA { for g_idx in 0..8 {
8- const
crates/circuits/src/blake.rs line 328 at r1 (raw file):
for g_idx in 0..8 { let [ai, bi, ci, di] = G_STATE_INDICES[g_idx]; let (na, nb, nc, nd) = blake_g_gate(
new_a, new_b
crates/circuits/src/blake.rs line 337 at r1 (raw file):
block[s[g_idx * 2 + 1] as usize], ); v[ai] = na;
can't we put it in the ouput directly?
crates/circuits/src/blake.rs line 346 at r1 (raw file):
// Finalize current compress: h[i] = old_h[i] ^ v[i] ^ v[i+8]. for i in 0..8 { h[i] = triple_xor_gate(ctx, old_h[i], v[i], v[i + 8]);
why do we put in h here and also do old_h = h ?
maybe it better to call it output
crates/circuits/src/blake.rs line 351 at r1 (raw file):
// 5. Apply reduce_to_m31: extract low/high from packed limbs via Simd::unpack_idx, then low + // high * 65536 in M31 arithmetic naturally reduces mod P.
Pack result in QM31
Code quote:
// 5. Apply reduce_to_m31: extract low/high from packed limbs via Simd::unpack_idx, then low +
// high * 65536 in M31 arithmetic naturally reduces mod P.crates/circuits/src/blake.rs line 352 at r1 (raw file):
// 5. Apply reduce_to_m31: extract low/high from packed limbs via Simd::unpack_idx, then low + // high * 65536 in M31 arithmetic naturally reduces mod P. let c65536 = ctx.constant(u32_to_qm31(65536));
call it 2^?
Code quote:
let c65536 = ctx.constant(u32_to_qm31(65536));crates/circuits/src/blake.rs line 354 at r1 (raw file):
let c65536 = ctx.constant(u32_to_qm31(65536)); let reduced: [Var; 8] = std::array::from_fn(|i| { let h_simd = Simd::from_packed(vec![*h[i].get()], 2);
maybe we can do all of them together?
crates/circuits/src/blake.rs line 360 at r1 (raw file):
}); // 6. Pack into 2 QM31 outputs.
remove the 6.
crates/circuits/src/blake_test.rs line 53 at r1 (raw file):
#[test] fn test_blake_from_gates() {
Please add a test that tests this Blake independently, because we will remove the other Blake in the near future.
Can you use those values for the messgae:
assert random\_message\[1\] = 1766240503;
assert random\_message\[2\] = 3660871006;
assert random\_message\[3\] = 388409270;
assert random\_message\[4\] = 1948594622;
assert random\_message\[5\] = 3119396969;
assert random\_message\[6\] = 3924579183;
assert random\_message\[7\] = 2089920034;
assert random\_message\[8\] = 3857888532;
assert random\_message\[9\] = 929304360;
assert random\_message\[10\] = 1810891574;
assert random\_message\[11\] = 860971754;
assert random\_message\[12\] = 1822893775;
assert random\_message\[13\] = 2008495810;
assert random\_message\[14\] = 2958962335;
assert random\_message\[15\] = 2340515744;
crates/circuits/src/circuit.rs line 209 at r1 (raw file):
/// Represents a Blake2s G mixing function gate. /// State words `a, b, c, d` are each a single QM31 wire with packed limbs: `(low_u16, high_u16, /// 0, 0)`. Message words `m0, m1` are single M31 wires `(value, 0, 0, 0)`.
also the messgae is (low and high)
crates/circuits/src/circuit.rs line 225 at r1 (raw file):
impl BlakeGGate { /// Reconstructs a u32 from a packed-limbs QM31: `low + high * 65536`. fn unpack_u32(v: QM31) -> u32 {
This function is defined twice
crates/circuits/src/circuit.rs line 231 at r1 (raw file):
/// Creates a packed-limbs QM31 from a u32: `(low_u16, high_u16, 0, 0)`. fn pack_u32(v: u32) -> QM31 { use crate::ivalue::qm31_from_u32s;
It doesn't make sense. just use the one that is outside the impl
use crate::ivalue::qm31_from_u32s;
crates/circuits/src/circuit.rs line 251 at r1 (raw file):
d = (d ^ a).rotate_right(8); c = c.wrapping_add(d); b = (b ^ c).rotate_right(7);
This should be exorted to a function and not defined twice
Code quote:
a = a.wrapping_add(b).wrapping_add(m0);
d = (d ^ a).rotate_right(16);
c = c.wrapping_add(d);
b = (b ^ c).rotate_right(12);
a = a.wrapping_add(b).wrapping_add(m1);
d = (d ^ a).rotate_right(8);
c = c.wrapping_add(d);
b = (b ^ c).rotate_right(7);crates/circuits/src/circuit.rs line 320 at r1 (raw file):
} /// Converts an M31 value `(x, 0, 0, 0)` to a [`U32Wrapper`](crate::wrappers::U32Wrapper)
remove the
Code quote:
`U32Wrapper`](crate::wrappers::U32Wrapper)crates/circuits/src/wrappers.rs line 72 at r1 (raw file):
/// Represents a u32 value stored as packed limbs in a QM31 wire: `(low_u16, high_u16, 0, 0)`. #[derive(Clone, Copy, PartialEq)] pub struct U32Wrapper<T>(T);
Can you explain why is this needed?
- Remove section header comments, numbered prefixes
- Extract blake_g_mixing() to deduplicate G mixing logic
- Extract read_u32() helper for U32Var -> u32 conversion
- Delete u32_to_qm31, use M31::from().into() directly
- Rename variables for clarity (prev_h, current_permutation, new_a/b/c/d)
- Extract constants (BLOCK_BYTES, WORDS_PER_BLOCK, N_G_CALLS_PER_ROUND)
- Fix docs (BlakeGGate message format, M31ToU32Gate, unpack_u32)
- Use crate::blake::{pack_u32, unpack_u32} in circuit.rs instead of duplicating
- Add independent test for blake_from_gates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review repliesComment 4a ( Comment 7 (U32Var input) — Done — added Comment 8 ( Comment 11 (batch Simd unpack) — Same number of gates either way, not an optimization. Comment 19 ( Comment 24 (put in output directly) — Rust doesn't allow destructuring a tuple into array indices. Comment 25 (why Comment 28 (batch Simd reduce) — Same as 11 — same gate count either way. Comment 36 (why U32Wrapper) — Type safety — marks that a Var holds packed-limbs All other comments have been addressed in commit fb5024d. |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alon-f
left a comment
There was a problem hiding this comment.
@alon-f made 36 comments.
Reviewable status: 2 of 8 files reviewed, 36 unresolved discussions (waiting on Gali-StarkWare, gilbens-starkware, ilyalesokhin-starkware, and Stavbe).
crates/circuits/src/blake.rs line 133 at r1 (raw file):
Previously, Stavbe wrote…
remove
Done.
crates/circuits/src/blake.rs line 164 at r1 (raw file):
Previously, Stavbe wrote…
remove
Done.
crates/circuits/src/blake.rs line 171 at r1 (raw file):
Previously, Stavbe wrote…
(limb_low, limb_high, 0, 0)
Done.
crates/circuits/src/blake.rs line 173 at r1 (raw file):
Previously, Stavbe wrote…
Replace the .0.0.0 it looks weird, don't we have getter for that?. Also please write the constant as 2^(
No getter in stwo's QM31 — nested tuple structs. Same pattern used throughout the codebase.
crates/circuits/src/blake.rs line 177 at r1 (raw file):
Previously, Stavbe wrote…
m31
Look at how the code looks now.
crates/circuits/src/blake.rs line 181 at r1 (raw file):
Previously, Stavbe wrote…
please remove
Done.
crates/circuits/src/blake.rs line 195 at r1 (raw file):
Previously, Stavbe wrote…
Why not updating the input for the helper function to be U32VAR
Done — added read_u32(ctx, v) helper.
crates/circuits/src/blake.rs line 202 at r1 (raw file):
Previously, Stavbe wrote…
it this mode P?
No, wrapping_add is mod 2^32 per Blake2s spec.
crates/circuits/src/blake.rs line 269 at r1 (raw file):
Previously, Stavbe wrote…
remove
and add all the comments that the other blake have
Done.
crates/circuits/src/blake.rs line 274 at r1 (raw file):
Previously, Stavbe wrote…
// Unpack each QM31 containing the messgae into U32Vars
Done.
crates/circuits/src/blake.rs line 277 at r1 (raw file):
Previously, Stavbe wrote…
Maybe we can do even more then 4 together
Same number of gates either way.
crates/circuits/src/blake.rs line 284 at r1 (raw file):
Previously, Stavbe wrote…
remove the 2.
Done.
crates/circuits/src/blake.rs line 285 at r1 (raw file):
Previously, Stavbe wrote…
const
Done.
crates/circuits/src/blake.rs line 286 at r1 (raw file):
Previously, Stavbe wrote…
const
Done.
crates/circuits/src/blake.rs line 293 at r1 (raw file):
Previously, Stavbe wrote…
Initlialize 'h'
Done.
crates/circuits/src/blake.rs line 295 at r1 (raw file):
Previously, Stavbe wrote…
Document: (config: no key, 32 bytes output).
Done.
crates/circuits/src/blake.rs line 299 at r1 (raw file):
Previously, Stavbe wrote…
remove the 4:
Done.
crates/circuits/src/blake.rs line 302 at r1 (raw file):
Previously, Stavbe wrote…
Document
Done
crates/circuits/src/blake.rs line 305 at r1 (raw file):
Previously, Stavbe wrote…
current_h. it is not ols
claude writes: Renamed to prev_h — clearer at the usage site since h is being overwritten.
I write: it's not current. the current h changes multiple times and we need the old h from before.
crates/circuits/src/blake.rs line 308 at r1 (raw file):
Previously, Stavbe wrote…
t can be u32 from the beginning
Done.
crates/circuits/src/blake.rs line 325 at r1 (raw file):
Previously, Stavbe wrote…
current_petmutation in &
(i write) I changed it to permutation. it's very weird to write current in a for loop. no one writes for current_i in range(n)
crates/circuits/src/blake.rs line 326 at r1 (raw file):
Previously, Stavbe wrote…
8- const
Done.
crates/circuits/src/blake.rs line 328 at r1 (raw file):
Previously, Stavbe wrote…
new_a, new_b
Done
crates/circuits/src/blake.rs line 337 at r1 (raw file):
Previously, Stavbe wrote…
can't we put it in the ouput directly?
Rust doesn't allow destructuring a tuple into array indices.
crates/circuits/src/blake.rs line 346 at r1 (raw file):
Previously, Stavbe wrote…
why do we put in h here and also do old_h = h ?
maybe it better to call it output
Blake2s finalization needs pre-compression h: h[i] = prev_h[i] ^ v[i] ^ v[i+8]. prev_h saves h before it gets overwritten.
crates/circuits/src/blake.rs line 351 at r1 (raw file):
Previously, Stavbe wrote…
Pack result in QM31
Done.
crates/circuits/src/blake.rs line 352 at r1 (raw file):
Previously, Stavbe wrote…
call it 2^?
Renamed to something else.
crates/circuits/src/blake.rs line 354 at r1 (raw file):
Previously, Stavbe wrote…
maybe we can do all of them together?
again, same gate count.
crates/circuits/src/blake.rs line 360 at r1 (raw file):
Previously, Stavbe wrote…
remove the 6.
Done.
crates/circuits/src/blake_test.rs line 53 at r1 (raw file):
Previously, Stavbe wrote…
Please add a test that tests this Blake independently, because we will remove the other Blake in the near future.
Can you use those values for the messgae:assert random\_message\[1\] = 1766240503; assert random\_message\[2\] = 3660871006; assert random\_message\[3\] = 388409270; assert random\_message\[4\] = 1948594622; assert random\_message\[5\] = 3119396969; assert random\_message\[6\] = 3924579183; assert random\_message\[7\] = 2089920034; assert random\_message\[8\] = 3857888532; assert random\_message\[9\] = 929304360; assert random\_message\[10\] = 1810891574; assert random\_message\[11\] = 860971754; assert random\_message\[12\] = 1822893775; assert random\_message\[13\] = 2008495810; assert random\_message\[14\] = 2958962335; assert random\_message\[15\] = 2340515744;
Done.
crates/circuits/src/circuit.rs line 209 at r1 (raw file):
Previously, Stavbe wrote…
also the messgae is (low and high)
Done.
crates/circuits/src/circuit.rs line 225 at r1 (raw file):
Previously, Stavbe wrote…
This function is defined twice
Done.
crates/circuits/src/circuit.rs line 231 at r1 (raw file):
Previously, Stavbe wrote…
It doesn't make sense. just use the one that is outside the impl
use crate::ivalue::qm31_from_u32s;
Done.
crates/circuits/src/circuit.rs line 251 at r1 (raw file):
Previously, Stavbe wrote…
This should be exorted to a function and not defined twice
Done.
crates/circuits/src/circuit.rs line 320 at r1 (raw file):
Previously, Stavbe wrote…
remove the
Done.
crates/circuits/src/wrappers.rs line 72 at r1 (raw file):
Previously, Stavbe wrote…
Can you explain why is this needed?
Type safety — marks that a Var holds packed-limbs (low_u16, high_u16, 0, 0), preventing accidental mixing with raw QM31 wires at compile time.
|
Suggestion: pub fn blake_from_gates<Value: IValue>(ctx: &mut Context<Value>, input: &[Var], n_bytes: usize) -> HashValue<Var> |
Stavbe
left a comment
There was a problem hiding this comment.
@Stavbe made 21 comments and resolved 35 discussions.
Reviewable status: 2 of 8 files reviewed, 23 unresolved discussions (waiting on alon-f, Gali-StarkWare, gilbens-starkware, and ilyalesokhin-starkware).
-- commits line 1 at r3:
need to be squash to one commit
crates/circuits/src/blake.rs line 163 at r3 (raw file):
[2, 7, 8, 13], [3, 4, 9, 14], ];
Please take all the const to the top og file
Code quote:
pub const BLAKE2S_IV: [u32; 8] = [
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
];
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
];
const N_G_CALLS_PER_ROUND: usize = 8;
/// The 8 (a,b,c,d) state index tuples per round: 4 columns then 4 diagonals.
pub const G_STATE_INDICES: [[usize; 4]; 8] = [
[0, 4, 8, 12],
[1, 5, 9, 13],
[2, 6, 10, 14],
[3, 7, 11, 15],
[0, 5, 10, 15],
[1, 6, 11, 12],
[2, 7, 8, 13],
[3, 4, 9, 14],
];crates/circuits/src/blake.rs line 176 at r3 (raw file):
} /// Computes one Blake2s G mixing step on u32 values: two half-rounds of add+xor+rotate.
/// Computes Blake G.
Code quote:
/// Computes one Blake2s G mixing step on u32 values: two half-rounds of add+xor+rotatecrates/circuits/src/blake.rs line 177 at r3 (raw file):
/// Computes one Blake2s G mixing step on u32 values: two half-rounds of add+xor+rotate. pub fn blake_g_mixing(
blak_g
Code quote:
blake_g_mixingcrates/circuits/src/blake.rs line 196 at r3 (raw file):
} type U32Var = U32Wrapper<Var>;
I am not sure this is the right place to define it.
Code quote:
type U32Var = U32Wrapper<Var>;crates/circuits/src/blake.rs line 200 at r3 (raw file):
/// Reads a [`U32Var`] from the context and unpacks it to a u32. fn read_u32(ctx: &TraceContext, v: U32Var) -> u32 { unpack_u32(ctx.get(*v.get()))
I don't think this function is called anywhere else but from here, so you can write the unpacking inline.
Code quote:
unpack_u32(ctx.get(*v.get()))crates/circuits/src/blake.rs line 277 at r3 (raw file):
} /// Adds a blake hash using decomposed gates to the circuit, and returns the two output variables
BlakeG gate, TripleXor gate and M31ToU32)
Code quote:
decomposed gatescrates/circuits/src/blake.rs line 277 at r3 (raw file):
} /// Adds a blake hash using decomposed gates to the circuit, and returns the two output variables
Please also mention the reduction that occurr to output (it is not a regular blake)3q
crates/circuits/src/blake.rs line 308 at r3 (raw file):
// Initialize h: IV XORed with the parameter block. // 0x01010020: depth=1, fanout=1, key_length=0, digest_length=32.
move this doc above the value
Code quote:
// 0x01010020: depth=1, fanout=1, key_length=0, digest_length=32.crates/circuits/src/blake.rs line 366 at r3 (raw file):
} // Pack result in QM31.
// Reduce each U32Var to M31.
Code quote:
// Pack result in QM31.crates/circuits/src/blake_test.rs line 82 at r3 (raw file):
#[test] fn test_blake_from_gates_independent() {
remove
Code quote:
_independentcrates/circuits/src/circuit.rs line 225 at r3 (raw file):
impl Gate for BlakeGGate { fn check(&self, values: &[QM31]) -> Result<(), String> { use crate::blake::{blake_g_mixing, pack_u32, unpack_u32};
Please move to the top of the file
Code quote:
use crate::blake::{blake_g_mixing, pack_u32, unpack_u32};crates/circuits/src/circuit.rs line 281 at r3 (raw file):
impl Gate for TripleXorGate { fn check(&self, values: &[QM31]) -> Result<(), String> { use crate::blake::{pack_u32, unpack_u32};
move the top of the file
Code quote:
use crate::blake::{pack_u32, unpack_u32};crates/circuits/src/circuit.rs line 303 at r3 (raw file):
} /// Converts an M31 value `(x, 0, 0, 0)` to packed-limbs representation `(low_u16, high_u15, 0, 0)`,
Represent a ... gate
crates/circuits/src/circuit.rs line 314 at r3 (raw file):
let input = values[self.input]; if input.0.1.0 != 0 || input.1.0.0 != 0 || input.1.1.0 != 0 { return Err(format!("SplitM31: input coords 1-3 not zero, got {input}"));
M31ToU32Gate: Expected M31, but got {input}.
Code quote:
"SplitM31: input coords 1-3 not zero, got {input}"crates/circuits/src/circuit.rs line 319 at r3 (raw file):
let out = values[self.out]; if out.1.0.0 != 0 || out.1.1.0 != 0 { return Err(format!("SplitM31: output coords 2-3 not zero, got {out}"));
[M31ToU32Gate: Expected U32, but got {out}
Code quote:
"SplitM31: output coords 2-3 not zero, got {out}"crates/circuits/src/circuit.rs line 323 at r3 (raw file):
let low = out.0.0.0; let high = out.0.1.0; if low + high * 65536 != x {
2^ ?
Code quote:
65536crates/circuits/src/circuit.rs line 324 at r3 (raw file):
let high = out.0.1.0; if low + high * 65536 != x { return Err(format!("SplitM31: {low} + {high} * 65536 != {x}"));
same
Code quote:
65536crates/circuits/src/circuit.rs line 324 at r3 (raw file):
let high = out.0.1.0; if low + high * 65536 != x { return Err(format!("SplitM31: {low} + {high} * 65536 != {x}"));
Name of the gate
Code quote:
SplitM31crates/circuits/src/circuit.rs line 327 at r3 (raw file):
} if low >= (1 << 16) { return Err(format!("SplitM31: low {low} >= 2^16"));
fix
Code quote:
"SplitM31crates/circuits/src/circuit.rs line 330 at r3 (raw file):
} if high >= (1 << 15) { return Err(format!("SplitM31: high {high} >= 2^15"));
fix
Code quote:
"SplitM31
…limbs representation
State words use packed u16 limbs in a single QM31: (low, high, 0, 0). All wire values are canonical M31. Input extraction uses Simd::unpack_idx. Introduces U32Wrapper type for type-safe u32 packed-limbs wires. BLAKE2S_IV moved from circuit_air to circuits::blake (single source of truth). Includes blake_from_gates() and comparison test against monolithic blake.