use std::collections::HashMap;
use triton_vm::prelude::*;
use twenty_first::math::traits::PrimitiveRootOfUnity as PRU;
use crate::prelude::*;
use crate::traits::basic_snippet::Reviewer;
use crate::traits::basic_snippet::SignOffFingerprint;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct PrimitiveRootOfUnity;
impl BasicSnippet for PrimitiveRootOfUnity {
fn parameters(&self) -> Vec<(DataType, String)> {
vec![(DataType::U64, "order".to_owned())]
}
fn return_values(&self) -> Vec<(DataType, String)> {
vec![(DataType::Bfe, "root_of_unity".to_string())]
}
fn entrypoint(&self) -> String {
"tasmlib_arithmetic_bfe_primitive_root_of_unity".to_string()
}
fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
let root_of_pow = |pow: u64| BFieldElement::primitive_root_of_unity(1 << pow).unwrap();
triton_asm!(
{self.entrypoint()}:
dup 0
split
pop 1
push 0
eq
assert error_id 142
dup 1
push 1
eq
dup 1
push 0
eq
mul
skiz
push {root_of_pow(32)}
dup 1
push 0
eq
assert error_id 140
dup 0 push 1 eq skiz push {root_of_pow(0)}
dup 0 push {1_u32 << 1} eq skiz push {root_of_pow(1)}
dup 0 push {1_u32 << 2} eq skiz push {root_of_pow(2)}
dup 0 push {1_u32 << 3} eq skiz push {root_of_pow(3)}
dup 0 push {1_u32 << 4} eq skiz push {root_of_pow(4)}
dup 0 push {1_u32 << 5} eq skiz push {root_of_pow(5)}
dup 0 push {1_u32 << 6} eq skiz push {root_of_pow(6)}
dup 0 push {1_u32 << 7} eq skiz push {root_of_pow(7)}
dup 0 push {1_u32 << 8} eq skiz push {root_of_pow(8)}
dup 0 push {1_u32 << 9} eq skiz push {root_of_pow(9)}
dup 0 push {1_u32 << 10} eq skiz push {root_of_pow(10)}
dup 0 push {1_u32 << 11} eq skiz push {root_of_pow(11)}
dup 0 push {1_u32 << 12} eq skiz push {root_of_pow(12)}
dup 0 push {1_u32 << 13} eq skiz push {root_of_pow(13)}
dup 0 push {1_u32 << 14} eq skiz push {root_of_pow(14)}
dup 0 push {1_u32 << 15} eq skiz push {root_of_pow(15)}
dup 0 push {1_u32 << 16} eq skiz push {root_of_pow(16)}
dup 0 push {1_u32 << 17} eq skiz push {root_of_pow(17)}
dup 0 push {1_u32 << 18} eq skiz push {root_of_pow(18)}
dup 0 push {1_u32 << 19} eq skiz push {root_of_pow(19)}
dup 0 push {1_u32 << 20} eq skiz push {root_of_pow(20)}
dup 0 push {1_u32 << 21} eq skiz push {root_of_pow(21)}
dup 0 push {1_u32 << 22} eq skiz push {root_of_pow(22)}
dup 0 push {1_u32 << 23} eq skiz push {root_of_pow(23)}
dup 0 push {1_u32 << 24} eq skiz push {root_of_pow(24)}
dup 0 push {1_u32 << 25} eq skiz push {root_of_pow(25)}
dup 0 push {1_u32 << 26} eq skiz push {root_of_pow(26)}
dup 0 push {1_u32 << 27} eq skiz push {root_of_pow(27)}
dup 0 push {1_u32 << 28} eq skiz push {root_of_pow(28)}
dup 0 push {1_u32 << 29} eq skiz push {root_of_pow(29)}
dup 0 push {1_u32 << 30} eq skiz push {root_of_pow(30)}
dup 0 push {1_u32 << 31} eq skiz push {root_of_pow(31)}
dup 0
push 1
eq
dup 1
split
pop 1
push 0
eq
push 0
eq
add
push 0
eq
push 0
eq
assert error_id 141
place 2
pop 2
return
)
}
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
let mut sign_offs = HashMap::new();
sign_offs.insert(Reviewer("ferdinand"), 0x7e7f78606c82b7d1.into());
sign_offs
}
}
#[cfg(test)]
mod tests {
use num_traits::Zero;
use super::*;
use crate::empty_stack;
use crate::test_prelude::*;
impl Closure for PrimitiveRootOfUnity {
type Args = u64;
fn rust_shadow(&self, stack: &mut Vec<BFieldElement>) {
let order = pop_encodable::<Self::Args>(stack);
assert!(!order.is_zero(), "No root of order 0 exists");
let root_of_unity = BFieldElement::primitive_root_of_unity(order).unwrap();
stack.push(root_of_unity);
}
fn pseudorandom_args(
&self,
seed: [u8; 32],
bench_case: Option<BenchmarkCase>,
) -> Self::Args {
match bench_case {
Some(BenchmarkCase::CommonCase) => 1_u64 << 10,
Some(BenchmarkCase::WorstCase) => 1 << 32,
None => 1 << StdRng::from_seed(seed).random_range(1..=32),
}
}
}
#[test]
fn primitive_root_of_order_2_pow_32_is_not_a_legal_order() {
let root = BFieldElement::primitive_root_of_unity(1 << 32).unwrap();
assert!(BFieldElement::primitive_root_of_unity(root.value()).is_none());
}
#[test]
fn all_primitive_roots_are_either_1_or_larger_than_u32_max() {
for pow in 1..=32 {
let root = BFieldElement::primitive_root_of_unity(1 << pow)
.unwrap()
.value();
assert!(root == 1 || root > u64::from(u32::MAX));
}
}
#[test]
fn primitive_root_of_unity_pbt() {
ShadowedClosure::new(PrimitiveRootOfUnity).test()
}
#[test]
fn primitive_root_of_unity_unit_test() {
for log2_order in 1..=32 {
let order = 1u64 << log2_order;
let mut init_stack = empty_stack();
for elem in order.encode().iter().rev() {
init_stack.push(*elem);
}
let expected = BFieldElement::primitive_root_of_unity(order).unwrap();
let expected_final_stack = [empty_stack(), vec![expected]].concat();
let _vm_output_state = test_rust_equivalence_given_complete_state(
&ShadowedClosure::new(PrimitiveRootOfUnity),
&init_stack,
&[],
&NonDeterminism::default(),
&None,
Some(&expected_final_stack),
);
}
}
#[test]
fn primitive_root_negative_test() {
let small_non_powers_of_two = (0_u64..100).filter(|x| !x.is_power_of_two());
let larger_non_powers_of_two = (1_u64..50).map(|x| (1 << 32) - x);
let too_large_powers_of_two = (33..64).map(|x| 1_u64 << x);
for order in small_non_powers_of_two
.chain(larger_non_powers_of_two)
.chain(too_large_powers_of_two)
{
dbg!(order);
let mut init_stack = empty_stack();
init_stack.extend(order.encode().iter().rev());
test_assertion_failure(
&ShadowedClosure::new(PrimitiveRootOfUnity),
InitVmState::with_stack(init_stack),
&[140, 141],
);
}
}
#[proptest]
fn triton_vm_crashes_if_order_lo_is_not_u32(
#[strategy(1_u8..=32)] log_2_order: u8,
#[strategy(0..=u32::MAX)]
#[map(u64::from)]
noise: u64,
) {
let [mut order_lo, order_hi] = (1_u64 << log_2_order).encode()[..] else {
unreachable!()
};
order_lo += bfe!(noise << 32);
prop_assume!((order_lo.value() >> 32) == noise);
test_assertion_failure(
&ShadowedClosure::new(PrimitiveRootOfUnity),
InitVmState::with_stack([empty_stack(), vec![order_hi, order_lo]].concat()),
&[142],
);
}
}
#[cfg(test)]
mod benches {
use super::*;
use crate::test_prelude::*;
#[test]
fn benchmark() {
ShadowedClosure::new(PrimitiveRootOfUnity).bench()
}
}