Skip to content

Commit 664c9d9

Browse files
fix: use seed for deterministic re-execution (#1741)
The `NonQrHintSubEx` phantom sub-executor now uses a fixed seed so that execution is deterministic between different instances of the VM. This is not strictly necessary since the constructed non-QR could be passed between runs for reproducibility, but it seems a better property that re-runs of the VM have the same behavior (e.g., for segmentation). The only additional consideration is that now you can potentially add moduli to the VM config that take a little longer for the VM to initialize - but making it truly take a long time seems to involve breaking generalized Riemann hypothesis. Lastly, the phantom sub-executors `DecompressHintSubEx` and `NonQrHintSubEx` in Weierstrass extension were already removed from the spec since we now use the phantom sub-executors in the modular arithmetic extension, but these variants were not deleted from the code. So they are deleted in this PR. @Avaneesh-axiom do you know if this was from a bad merge previously?
1 parent 1c4a696 commit 664c9d9

File tree

6 files changed

+15
-258
lines changed

6 files changed

+15
-258
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/algebra/circuit/src/modular_extension.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use openvm_instructions::{LocalOpcode, PhantomDiscriminant, VmOpcode};
1616
use openvm_mod_circuit_builder::ExprBuilderConfig;
1717
use openvm_rv32_adapters::{Rv32IsEqualModAdapterChip, Rv32VecHeapAdapterChip};
1818
use openvm_stark_backend::p3_field::PrimeField32;
19-
use rand::{rngs::StdRng, SeedableRng};
19+
use rand::Rng;
2020
use serde::{Deserialize, Serialize};
2121
use serde_with::{serde_as, DisplayFromStr};
2222
use strum::EnumCount;
@@ -263,6 +263,7 @@ pub(crate) mod phantom {
263263
use openvm_instructions::{riscv::RV32_MEMORY_AS, PhantomDiscriminant};
264264
use openvm_rv32im_circuit::adapters::unsafe_read_rv32_register;
265265
use openvm_stark_backend::p3_field::PrimeField32;
266+
use rand::{rngs::StdRng, SeedableRng};
266267

267268
use super::{find_non_qr, mod_sqrt};
268269

@@ -353,7 +354,15 @@ pub(crate) mod phantom {
353354

354355
impl NonQrHintSubEx {
355356
pub fn new(supported_moduli: Vec<BigUint>) -> Self {
356-
let non_qrs = supported_moduli.iter().map(find_non_qr).collect();
357+
// Use deterministic seed so that the non-QR are deterministic between different
358+
// instances of the VM. The seed determines the runtime of Tonelli-Shanks, if the
359+
// algorithm is necessary, which affects the time it takes to construct and initialize
360+
// the VM but does not affect the runtime.
361+
let mut rng = StdRng::from_seed([0u8; 32]);
362+
let non_qrs = supported_moduli
363+
.iter()
364+
.map(|modulus| find_non_qr(modulus, &mut rng))
365+
.collect();
357366
Self {
358367
supported_moduli,
359368
non_qrs,
@@ -457,7 +466,7 @@ pub fn mod_sqrt(x: &BigUint, modulus: &BigUint, non_qr: &BigUint) -> Option<BigU
457466
}
458467

459468
// Returns a non-quadratic residue in the field
460-
pub fn find_non_qr(modulus: &BigUint) -> BigUint {
469+
pub fn find_non_qr(modulus: &BigUint, rng: &mut impl Rng) -> BigUint {
461470
if modulus % 4u32 == BigUint::from(3u8) {
462471
// p = 3 mod 4 then -1 is a quadratic residue
463472
modulus - BigUint::one()
@@ -466,7 +475,6 @@ pub fn find_non_qr(modulus: &BigUint) -> BigUint {
466475
// since 2^((p-1)/2) = (-1)^((p^2-1)/8)
467476
BigUint::from_u8(2u8).unwrap()
468477
} else {
469-
let mut rng = StdRng::from_entropy();
470478
let mut non_qr = rng.gen_biguint_range(
471479
&BigUint::from_u8(2).unwrap(),
472480
&(modulus - BigUint::from_u8(1).unwrap()),

extensions/ecc/circuit/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ strum = { workspace = true }
2626
derive_more = { workspace = true }
2727
derive-new = { workspace = true }
2828
once_cell = { workspace = true, features = ["std"] }
29-
eyre = { workspace = true }
30-
num-integer = { workspace = true }
3129
serde = { workspace = true }
3230
serde_with = { workspace = true }
3331
lazy_static = { workspace = true }

extensions/ecc/circuit/src/weierstrass_extension.rs

Lines changed: 2 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use openvm_circuit_primitives::bitwise_op_lookup::{
1313
BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip,
1414
};
1515
use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter};
16-
use openvm_ecc_transpiler::{EccPhantom, Rv32WeierstrassOpcode};
17-
use openvm_instructions::{LocalOpcode, PhantomDiscriminant, VmOpcode};
16+
use openvm_ecc_transpiler::Rv32WeierstrassOpcode;
17+
use openvm_instructions::{LocalOpcode, VmOpcode};
1818
use openvm_mod_circuit_builder::ExprBuilderConfig;
1919
use openvm_rv32_adapters::Rv32VecHeapAdapterChip;
2020
use openvm_stark_backend::p3_field::PrimeField32;
@@ -224,228 +224,11 @@ impl<F: PrimeField32> VmExtension<F> for WeierstrassExtension {
224224
panic!("Modulus too large");
225225
}
226226
}
227-
let non_qr_hint_sub_ex = phantom::NonQrHintSubEx::new(self.supported_curves.clone());
228-
builder.add_phantom_sub_executor(
229-
non_qr_hint_sub_ex.clone(),
230-
PhantomDiscriminant(EccPhantom::HintNonQr as u16),
231-
)?;
232-
builder.add_phantom_sub_executor(
233-
phantom::DecompressHintSubEx::new(non_qr_hint_sub_ex),
234-
PhantomDiscriminant(EccPhantom::HintDecompress as u16),
235-
)?;
236227

237228
Ok(inventory)
238229
}
239230
}
240231

241-
pub(crate) mod phantom {
242-
use std::{
243-
iter::{once, repeat},
244-
ops::Deref,
245-
};
246-
247-
use eyre::bail;
248-
use num_bigint::BigUint;
249-
use num_integer::Integer;
250-
use num_traits::One;
251-
use openvm_algebra_circuit::{find_non_qr, mod_sqrt};
252-
use openvm_circuit::{
253-
arch::{PhantomSubExecutor, Streams},
254-
system::memory::MemoryController,
255-
};
256-
use openvm_instructions::{riscv::RV32_MEMORY_AS, PhantomDiscriminant};
257-
use openvm_rv32im_circuit::adapters::unsafe_read_rv32_register;
258-
use openvm_stark_backend::p3_field::PrimeField32;
259-
260-
use super::CurveConfig;
261-
262-
// Hint for a decompression
263-
// if possible is true, then `sqrt` is the decompressed y-coordinate
264-
// if possible is false, then `sqrt` is such that
265-
// `sqrt^2 = rhs * non_qr` where `rhs` is the rhs of the curve equation
266-
pub struct DecompressionHint<T> {
267-
pub possible: bool,
268-
pub sqrt: T,
269-
}
270-
271-
#[derive(derive_new::new)]
272-
pub struct DecompressHintSubEx(NonQrHintSubEx);
273-
274-
impl Deref for DecompressHintSubEx {
275-
type Target = NonQrHintSubEx;
276-
277-
fn deref(&self) -> &NonQrHintSubEx {
278-
&self.0
279-
}
280-
}
281-
282-
impl<F: PrimeField32> PhantomSubExecutor<F> for DecompressHintSubEx {
283-
fn phantom_execute(
284-
&mut self,
285-
memory: &MemoryController<F>,
286-
streams: &mut Streams<F>,
287-
_: PhantomDiscriminant,
288-
a: F,
289-
b: F,
290-
c_upper: u16,
291-
) -> eyre::Result<()> {
292-
let c_idx = c_upper as usize;
293-
if c_idx >= self.supported_curves.len() {
294-
bail!(
295-
"Curve index {c_idx} out of range: {} supported curves",
296-
self.supported_curves.len()
297-
);
298-
}
299-
let curve = &self.supported_curves[c_idx];
300-
let rs1 = unsafe_read_rv32_register(memory, a);
301-
let num_limbs: usize = if curve.modulus.bits().div_ceil(8) <= 32 {
302-
32
303-
} else if curve.modulus.bits().div_ceil(8) <= 48 {
304-
48
305-
} else {
306-
bail!("Modulus too large")
307-
};
308-
let mut x_limbs: Vec<u8> = Vec::with_capacity(num_limbs);
309-
for i in 0..num_limbs {
310-
let limb = memory.unsafe_read_cell(
311-
F::from_canonical_u32(RV32_MEMORY_AS),
312-
F::from_canonical_u32(rs1 + i as u32),
313-
);
314-
x_limbs.push(limb.as_canonical_u32() as u8);
315-
}
316-
let x = BigUint::from_bytes_le(&x_limbs);
317-
let rs2 = unsafe_read_rv32_register(memory, b);
318-
let rec_id = memory.unsafe_read_cell(
319-
F::from_canonical_u32(RV32_MEMORY_AS),
320-
F::from_canonical_u32(rs2),
321-
);
322-
let hint = self.decompress_point(x, rec_id.as_canonical_u32() & 1 == 1, c_idx);
323-
let hint_bytes = once(F::from_bool(hint.possible))
324-
.chain(repeat(F::ZERO))
325-
.take(4)
326-
.chain(
327-
hint.sqrt
328-
.to_bytes_le()
329-
.into_iter()
330-
.map(F::from_canonical_u8)
331-
.chain(repeat(F::ZERO))
332-
.take(num_limbs),
333-
)
334-
.collect();
335-
streams.hint_stream = hint_bytes;
336-
Ok(())
337-
}
338-
}
339-
340-
impl DecompressHintSubEx {
341-
/// Given `x` in the coordinate field of the curve, and the recovery id,
342-
/// return the unique `y` such that `(x, y)` is a point on the curve and
343-
/// `y` has the same parity as the recovery id.
344-
///
345-
/// If no such `y` exists, return the square root of `(x^3 + ax + b) * non_qr`
346-
/// where `non_qr` is a quadratic nonresidue of the field.
347-
fn decompress_point(
348-
&self,
349-
x: BigUint,
350-
is_y_odd: bool,
351-
curve_idx: usize,
352-
) -> DecompressionHint<BigUint> {
353-
let curve = &self.supported_curves[curve_idx];
354-
let alpha = ((&x * &x * &x) + (&x * &curve.a) + &curve.b) % &curve.modulus;
355-
match mod_sqrt(&alpha, &curve.modulus, &self.non_qrs[curve_idx]) {
356-
Some(beta) => {
357-
if is_y_odd == beta.is_odd() {
358-
DecompressionHint {
359-
possible: true,
360-
sqrt: beta,
361-
}
362-
} else {
363-
DecompressionHint {
364-
possible: true,
365-
sqrt: &curve.modulus - &beta,
366-
}
367-
}
368-
}
369-
None => {
370-
debug_assert_eq!(
371-
self.non_qrs[curve_idx]
372-
.modpow(&((&curve.modulus - BigUint::one()) >> 1), &curve.modulus),
373-
&curve.modulus - BigUint::one()
374-
);
375-
let sqrt = mod_sqrt(
376-
&(&alpha * &self.non_qrs[curve_idx]),
377-
&curve.modulus,
378-
&self.non_qrs[curve_idx],
379-
)
380-
.unwrap();
381-
DecompressionHint {
382-
possible: false,
383-
sqrt,
384-
}
385-
}
386-
}
387-
}
388-
}
389-
390-
#[derive(Clone)]
391-
pub struct NonQrHintSubEx {
392-
pub supported_curves: Vec<CurveConfig>,
393-
pub non_qrs: Vec<BigUint>,
394-
}
395-
396-
impl NonQrHintSubEx {
397-
pub fn new(supported_curves: Vec<CurveConfig>) -> Self {
398-
let non_qrs = supported_curves
399-
.iter()
400-
.map(|curve| find_non_qr(&curve.modulus))
401-
.collect();
402-
Self {
403-
supported_curves,
404-
non_qrs,
405-
}
406-
}
407-
}
408-
409-
impl<F: PrimeField32> PhantomSubExecutor<F> for NonQrHintSubEx {
410-
fn phantom_execute(
411-
&mut self,
412-
_: &MemoryController<F>,
413-
streams: &mut Streams<F>,
414-
_: PhantomDiscriminant,
415-
_: F,
416-
_: F,
417-
c_upper: u16,
418-
) -> eyre::Result<()> {
419-
let c_idx = c_upper as usize;
420-
if c_idx >= self.supported_curves.len() {
421-
bail!(
422-
"Curve index {c_idx} out of range: {} supported curves",
423-
self.supported_curves.len()
424-
);
425-
}
426-
let curve = &self.supported_curves[c_idx];
427-
428-
let num_limbs: usize = if curve.modulus.bits().div_ceil(8) <= 32 {
429-
32
430-
} else if curve.modulus.bits().div_ceil(8) <= 48 {
431-
48
432-
} else {
433-
bail!("Modulus too large")
434-
};
435-
436-
let hint_bytes = self.non_qrs[c_idx]
437-
.to_bytes_le()
438-
.into_iter()
439-
.map(F::from_canonical_u8)
440-
.chain(repeat(F::ZERO))
441-
.take(num_limbs)
442-
.collect();
443-
streams.hint_stream = hint_bytes;
444-
Ok(())
445-
}
446-
}
447-
}
448-
449232
// Convenience constants for constructors
450233
lazy_static! {
451234
// The constants are taken from: https://en.bitcoin.it/wiki/Secp256k1

extensions/ecc/guest/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ pub enum SwBaseFunct7 {
3232
SwAddNe = 0,
3333
SwDouble,
3434
SwSetup,
35-
HintDecompress,
36-
HintNonQr,
3735
}
3836

3937
impl SwBaseFunct7 {

extensions/ecc/transpiler/src/lib.rs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use openvm_ecc_guest::{SwBaseFunct7, OPCODE, SW_FUNCT3};
22
use openvm_instructions::{
3-
instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode, PhantomDiscriminant,
4-
VmOpcode,
3+
instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode, VmOpcode,
54
};
65
use openvm_instructions_derive::LocalOpcode;
76
use openvm_stark_backend::p3_field::PrimeField32;
@@ -22,13 +21,6 @@ pub enum Rv32WeierstrassOpcode {
2221
SETUP_EC_DOUBLE,
2322
}
2423

25-
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromRepr)]
26-
#[repr(u16)]
27-
pub enum EccPhantom {
28-
HintDecompress = 0x40,
29-
HintNonQr = 0x41,
30-
}
31-
3224
#[derive(Default)]
3325
pub struct EccTranspilerExtension;
3426

@@ -58,26 +50,6 @@ impl<F: PrimeField32> TranspilerExtension<F> for EccTranspilerExtension {
5850
let curve_idx =
5951
((dec_insn.funct7 as u8) / SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS) as usize;
6052
let curve_idx_shift = curve_idx * Rv32WeierstrassOpcode::COUNT;
61-
if let Some(SwBaseFunct7::HintDecompress) = SwBaseFunct7::from_repr(base_funct7) {
62-
assert_eq!(dec_insn.rd, 0);
63-
return Some(TranspilerOutput::one_to_one(Instruction::phantom(
64-
PhantomDiscriminant(EccPhantom::HintDecompress as u16),
65-
F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs1),
66-
F::from_canonical_usize(RV32_REGISTER_NUM_LIMBS * dec_insn.rs2),
67-
curve_idx as u16,
68-
)));
69-
}
70-
if let Some(SwBaseFunct7::HintNonQr) = SwBaseFunct7::from_repr(base_funct7) {
71-
assert_eq!(dec_insn.rd, 0);
72-
assert_eq!(dec_insn.rs1, 0);
73-
assert_eq!(dec_insn.rs2, 0);
74-
return Some(TranspilerOutput::one_to_one(Instruction::phantom(
75-
PhantomDiscriminant(EccPhantom::HintNonQr as u16),
76-
F::ZERO,
77-
F::ZERO,
78-
curve_idx as u16,
79-
)));
80-
}
8153
if base_funct7 == SwBaseFunct7::SwSetup as u8 {
8254
let local_opcode = match dec_insn.rs2 {
8355
0 => Rv32WeierstrassOpcode::SETUP_EC_DOUBLE,

0 commit comments

Comments
 (0)