use tasm_lib::data_type::DataType;
use tasm_lib::library::StaticAllocation;
use tasm_lib::prelude::BasicSnippet;
use tasm_lib::prelude::Library;
use tasm_lib::triton_vm::twenty_first::tip5::Digest;
use triton_vm::isa::instruction::LabelledInstruction;
use triton_vm::isa::triton_asm;
use triton_vm::prelude::BFieldElement;
use triton_vm::twenty_first::bfe;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct DigestToU320;
impl DigestToU320 {
pub const NUM_LIMBS: usize = 2 * Digest::LEN;
}
impl BasicSnippet for DigestToU320 {
fn parameters(&self) -> Vec<(DataType, String)> {
vec![(DataType::Digest, "digest".to_string())]
}
fn return_values(&self) -> Vec<(DataType, String)> {
let limbs = DataType::Tuple(vec![DataType::U32; Self::NUM_LIMBS]);
vec![(limbs, "limbs".to_string())]
}
fn entrypoint(&self) -> String {
"tasmlib_arithmetic_digest_to_u320".to_string()
}
fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
let parts = library.kmalloc(Self::NUM_LIMBS as u32);
let limbs = library.kmalloc(Self::NUM_LIMBS as u32);
let cell = |alloc: &StaticAllocation, k: usize| alloc.write_address() + bfe!(k as u64);
let read = |alloc: &StaticAllocation, k: usize| triton_asm!(push {cell(alloc, k)} read_mem 1 pop 1);
let write = |alloc: &StaticAllocation, k: usize| triton_asm!(push {cell(alloc, k)} write_mem 1 pop 1);
let zero_limbs = triton_asm!(
push 0 push 0 push 0 push 0 push 0
push {limbs.write_address()} write_mem 5
push 0 push 0 push 0 push 0 push 0
pick 5 write_mem 5 pop 1
);
let store_parts = (0..Digest::LEN)
.flat_map(|i| triton_asm!(split push {cell(&parts, 2 * i)} write_mem 2 pop 1))
.collect::<Vec<_>>();
let two_pow_32_minus_one = u64::from(u32::MAX);
let horner_step = |i: usize| {
let limb_step = |k: usize| {
let add_part = if k < 2 {
triton_asm!({&read(&parts, 2 * i + k)} add)
} else {
triton_asm!()
};
triton_asm!(
{&read(&limbs, k)}
dup 0 push {two_pow_32_minus_one} mul dup 4 add split
pick 5 pop 1
pick 1 place 4
pick 3 pick 2 add pick 2 add
{&add_part}
split
{&write(&limbs, k)}
)
};
let limb_steps = (0..Self::NUM_LIMBS).flat_map(limb_step).collect::<Vec<_>>();
triton_asm!(
push 0 push 0 push 0
{&limb_steps}
pop 3
)
};
let horner = (0..Digest::LEN - 1)
.rev()
.flat_map(horner_step)
.collect::<Vec<_>>();
let push_limbs = triton_asm!(
push {limbs.read_address()} read_mem 5 read_mem 5 pop 1
);
triton_asm!(
{self.entrypoint()}:
{&zero_limbs}
{&store_parts}
{&read(&parts, Self::NUM_LIMBS - 2)}
{&write(&limbs, 0)}
{&read(&parts, Self::NUM_LIMBS - 1)}
{&write(&limbs, 1)}
{&horner}
{&push_limbs}
return
)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use num_bigint::BigUint;
use rand::RngExt;
use rand::SeedableRng;
use rand::prelude::StdRng;
use tasm_lib::library::STATIC_MEMORY_FIRST_ADDRESS;
use tasm_lib::pop_encodable;
use tasm_lib::push_encodable;
use tasm_lib::snippet_bencher::BenchmarkCase;
use tasm_lib::traits::function::Function;
use tasm_lib::traits::function::FunctionInitialState;
use tasm_lib::traits::function::ShadowedFunction;
use tasm_lib::traits::rust_shadow::RustShadow;
use tasm_lib::traits::rust_shadow::RustShadowError;
use triton_vm::twenty_first::math::b_field_element::BFieldElement;
use super::*;
fn parts_address() -> BFieldElement {
STATIC_MEMORY_FIRST_ADDRESS - bfe!(9)
}
fn limbs_address() -> BFieldElement {
STATIC_MEMORY_FIRST_ADDRESS - bfe!(19)
}
impl Function for DigestToU320 {
fn rust_shadow(
&self,
stack: &mut Vec<BFieldElement>,
memory: &mut HashMap<BFieldElement, BFieldElement>,
) -> Result<(), RustShadowError> {
let digest: Digest = pop_encodable(stack)?;
for (i, element) in digest.values().into_iter().enumerate() {
let (lo, hi) = (element.value() & u64::from(u32::MAX), element.value() >> 32);
memory.insert(parts_address() + bfe!(2 * i), bfe!(lo));
memory.insert(parts_address() + bfe!(2 * i + 1), bfe!(hi));
}
let mut limbs = BigUint::from(digest).to_u32_digits();
limbs.resize(Self::NUM_LIMBS, 0);
for (k, limb) in limbs.iter().enumerate() {
memory.insert(limbs_address() + bfe!(k), bfe!(*limb));
}
let limbs: [u32; Self::NUM_LIMBS] = limbs.try_into().unwrap();
push_encodable(stack, &limbs);
Ok(())
}
fn pseudorandom_initial_state(
&self,
seed: [u8; 32],
_: Option<BenchmarkCase>,
) -> FunctionInitialState {
let digest: Digest = StdRng::from_seed(seed).random();
self.initial_state(digest)
}
fn corner_case_initial_states(&self) -> Vec<FunctionInitialState> {
let max = Digest::new([BFieldElement::new(BFieldElement::MAX); Digest::LEN]);
[Digest::default(), max]
.map(|digest| self.initial_state(digest))
.to_vec()
}
}
impl DigestToU320 {
fn initial_state(&self, digest: Digest) -> FunctionInitialState {
let mut stack = self.init_stack_for_isolated_run();
push_encodable(&mut stack, &digest);
FunctionInitialState {
stack,
memory: HashMap::default(),
}
}
}
#[test]
fn rust_and_tasm_agree() {
ShadowedFunction::new(DigestToU320).test()
}
}