From d8627f29b077a87dec68ee0ba2b686fc43febdec Mon Sep 17 00:00:00 2001 From: Nikolay Kostadinov <118672723+NikolayKostadinov21@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:23:30 +0200 Subject: [PATCH] feat: Implement compute_shuffled_index in plonky2x and add abstracted functions for plonky2x in the utils file (#248) * Implement compute_shuffled_index with plonky2x. --------- Co-authored-by: Dimo99 Co-authored-by: Aneta Tsvetkova --- casper-finality-proofs/Cargo.toml | 4 + .../bin/compute_shuffled_index.rs | 43 +++++++ .../src/compute_shuffled_index/circuit.rs | 31 +++++ .../src/compute_shuffled_index/helpers.rs | 113 ++++++++++++++++++ .../src/compute_shuffled_index/mod.rs | 3 + casper-finality-proofs/src/lib.rs | 1 + .../types/compute_shuffled_index_data.rs | 10 ++ .../src/test_engine/types/mod.rs | 1 + .../src/test_engine/utils/setup.rs | 30 +++++ .../wrappers/compute_shuffled_index/mod.rs | 2 + .../compute_shuffled_index/wrapper_mainnet.rs | 39 ++++++ .../compute_shuffled_index/wrapper_minimal.rs | 39 ++++++ .../src/test_engine/wrappers/mod.rs | 1 + casper-finality-proofs/src/utils/bits.rs | 19 --- casper-finality-proofs/src/utils/mod.rs | 3 +- .../src/utils/plonky2x_extensions.rs | 65 +++++++++- .../justification_bits.rs | 2 +- 17 files changed, 383 insertions(+), 23 deletions(-) create mode 100644 casper-finality-proofs/bin/compute_shuffled_index.rs create mode 100644 casper-finality-proofs/src/compute_shuffled_index/circuit.rs create mode 100644 casper-finality-proofs/src/compute_shuffled_index/helpers.rs create mode 100644 casper-finality-proofs/src/compute_shuffled_index/mod.rs create mode 100644 casper-finality-proofs/src/test_engine/types/compute_shuffled_index_data.rs create mode 100644 casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/mod.rs create mode 100644 casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_mainnet.rs create mode 100644 casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_minimal.rs delete mode 100644 casper-finality-proofs/src/utils/bits.rs diff --git a/casper-finality-proofs/Cargo.toml b/casper-finality-proofs/Cargo.toml index 7d0b29ab6..550466789 100644 --- a/casper-finality-proofs/Cargo.toml +++ b/casper-finality-proofs/Cargo.toml @@ -9,6 +9,10 @@ edition = "2021" name = "weigh_justification_and_finalization" path = "bin/weigh_justification_and_finalization.rs" +[[bin]] +name = "compute_shuffled_index" +path = "bin/compute_shuffled_index.rs" + [[bin]] name = "test_engine" path = "src/test_engine/bin/main.rs" diff --git a/casper-finality-proofs/bin/compute_shuffled_index.rs b/casper-finality-proofs/bin/compute_shuffled_index.rs new file mode 100644 index 000000000..bc52837ca --- /dev/null +++ b/casper-finality-proofs/bin/compute_shuffled_index.rs @@ -0,0 +1,43 @@ +use casper_finality_proofs::compute_shuffled_index::circuit::define; +use plonky2x::prelude::{ + bytes, ArrayVariable, ByteVariable, CircuitBuilder, DefaultParameters, U64Variable, +}; +use plonky2x::utils; + +fn main() { + utils::setup_logger(); + + let seed_bytes: Vec = + bytes!("0x4ac96f664a6cafd300b161720809b9e17905d4d8fed7a97ff89cf0080a953fe7"); + + let seed_bytes_fixed_size: [u8; 32] = seed_bytes.try_into().unwrap(); + + const SHUFFLE_ROUND_COUNT: u8 = 90; + let mut builder = CircuitBuilder::::new(); + define(&mut builder, SHUFFLE_ROUND_COUNT); + + let circuit = builder.mock_build(); + + const START_IDX: u64 = 0; + const COUNT: u64 = 100; + let mapping = [ + 53, 21, 19, 29, 76, 32, 67, 63, 3, 38, 89, 37, 30, 78, 0, 40, 96, 44, 22, 42, 23, 62, 92, + 87, 11, 43, 54, 75, 71, 82, 68, 36, 59, 90, 66, 45, 58, 70, 4, 72, 33, 24, 6, 39, 52, 51, + 99, 8, 27, 88, 20, 31, 86, 77, 94, 95, 85, 41, 93, 15, 13, 5, 74, 81, 18, 17, 47, 2, 16, 7, + 84, 9, 79, 65, 61, 49, 60, 50, 64, 34, 55, 56, 91, 98, 28, 46, 14, 73, 12, 25, 26, 57, 83, + 80, 35, 97, 69, 10, 1, 48, + ]; + for i in START_IDX..COUNT { + let mut input = circuit.input(); + + input.write::(i); + input.write::(COUNT); + input.write::>(seed_bytes_fixed_size.to_vec()); + + let (_witness, mut _output) = circuit.mock_prove(&input); + let shuffled_index_res = _output.read::(); + + println!("{} {}", mapping[i as usize], shuffled_index_res); + assert!(mapping[i as usize] == shuffled_index_res); + } +} diff --git a/casper-finality-proofs/src/compute_shuffled_index/circuit.rs b/casper-finality-proofs/src/compute_shuffled_index/circuit.rs new file mode 100644 index 000000000..e99076cfe --- /dev/null +++ b/casper-finality-proofs/src/compute_shuffled_index/circuit.rs @@ -0,0 +1,31 @@ +use crate::utils::plonky2x_extensions::{assert_is_true, max}; +use plonky2x::prelude::{Bytes32Variable, CircuitBuilder, PlonkParameters, U64Variable}; + +use super::helpers::{compute_bit, compute_byte, compute_flip, compute_pivot, compute_source}; + +pub fn define, const D: usize>( + builder: &mut CircuitBuilder, + shuffle_round_count: u8, +) { + let mut index = builder.read::(); + let index_count = builder.read::(); + let seed = builder.read::(); + + let index_lt_index_count = builder.lt(index, index_count); + assert_is_true(builder, index_lt_index_count); + + for current_round in 0..shuffle_round_count { + let pivot = compute_pivot(builder, seed, index_count, current_round); + let flip = compute_flip(builder, pivot, index_count, index); + + let position = max(builder, index, flip); + let source = compute_source(builder, position, seed, current_round); + + let byte = compute_byte(builder, source, position); + let bit = compute_bit(builder, byte, position); + + index = builder.select(bit, flip, index); + } + + builder.write::(index); +} diff --git a/casper-finality-proofs/src/compute_shuffled_index/helpers.rs b/casper-finality-proofs/src/compute_shuffled_index/helpers.rs new file mode 100644 index 000000000..6a6d89317 --- /dev/null +++ b/casper-finality-proofs/src/compute_shuffled_index/helpers.rs @@ -0,0 +1,113 @@ +use itertools::Itertools; +use plonky2::field::types::Field; +use plonky2x::{ + frontend::vars::EvmVariable, + prelude::{ + BoolVariable, ByteVariable, Bytes32Variable, CircuitBuilder, CircuitVariable, + PlonkParameters, U64Variable, Variable, + }, +}; + +use crate::utils::plonky2x_extensions::{bits_to_variable, exp_from_bits}; + +/// Returns the first 8 bytes of the hashed concatenation of seed with current_round +pub fn compute_pivot, const D: usize>( + builder: &mut CircuitBuilder, + seed: Bytes32Variable, + index_count: U64Variable, + current_round: u8, +) -> U64Variable { + let current_round_byte: ByteVariable = ByteVariable::constant(builder, current_round); + let concatenation = [seed.as_bytes().as_slice(), &[current_round_byte]] + .concat() + .to_vec(); + + let hash = builder.curta_sha256(&concatenation); + + let hash = U64Variable::decode( + builder, + &hash.as_bytes()[0..8] + .into_iter() + .rev() + .cloned() + .collect_vec(), + ); + + builder.rem(hash, index_count) +} + +/// Returns the computation of (pivot + index_count - index) % index_count +pub fn compute_flip, const D: usize>( + builder: &mut CircuitBuilder, + pivot: U64Variable, + index_count: U64Variable, + index: U64Variable, +) -> U64Variable { + let sum_pivot_index_count = builder.add(pivot, index_count); + let sum_pivot_index_count_sub_index = builder.sub(sum_pivot_index_count, index); + + builder.rem(sum_pivot_index_count_sub_index, index_count) +} + +/// Returns the hashed concatenation of seed, current_round and position divided by 256 +pub fn compute_source, const D: usize>( + builder: &mut CircuitBuilder, + position: U64Variable, + seed: Bytes32Variable, + current_round: u8, +) -> Bytes32Variable { + let current_round_byte = ByteVariable::constant(builder, current_round as u8); + let const_256 = builder.constant::(256); + let position_div_256 = builder.div(position, const_256); + let position_div_256_bytes = builder + .to_le_bits(position_div_256) + .chunks(8) + .take(4) + .map(|byte| ByteVariable(byte.iter().rev().cloned().collect_vec().try_into().unwrap())) + .collect_vec(); + + builder.curta_sha256( + &[ + seed.as_bytes().as_slice(), + &[current_round_byte], + position_div_256_bytes.as_slice(), + ] + .concat(), + ) +} + +/// Returns the byte in source at index (position % 256) / 8 +pub fn compute_byte, const D: usize>( + builder: &mut CircuitBuilder, + source_array: Bytes32Variable, + position: U64Variable, +) -> ByteVariable { + let const_8 = builder.constant::(8); + let const_256 = builder.constant::(256); + let position_mod_256 = builder.rem(position, const_256); + let position_mod_256_div_8 = builder.div(position_mod_256, const_8); + let position_mod_256_div_8_bits = builder.to_le_bits(position_mod_256_div_8); + let position_mod_256_div_8_variable = bits_to_variable(builder, &position_mod_256_div_8_bits); + + builder.select_array(&source_array.0 .0, position_mod_256_div_8_variable) +} + +/// Returns the remainder of byte / 2^(position % 8) and 2 as BoolVariable +pub fn compute_bit, const D: usize>( + builder: &mut CircuitBuilder, + byte: ByteVariable, + position: U64Variable, +) -> BoolVariable { + let const_0: Variable = builder.constant(L::Field::from_canonical_usize(0)); + let const_2: Variable = builder.constant(L::Field::from_canonical_usize(2)); + let byte_to_variable = byte.to_variable(builder); + let byte_u64 = U64Variable::from_variables(builder, &[byte_to_variable, const_0]); + + let position_first_3_bits = &builder.to_le_bits(position)[..3]; + let const_2_pow_position_first_3_bits = exp_from_bits(builder, const_2, &position_first_3_bits); + let const_2_pow_position_first_3_bits_u64 = + U64Variable::from_variables(builder, &[const_2_pow_position_first_3_bits, const_0]); + let bit = builder.div(byte_u64, const_2_pow_position_first_3_bits_u64); + + builder.to_le_bits(bit)[0] +} diff --git a/casper-finality-proofs/src/compute_shuffled_index/mod.rs b/casper-finality-proofs/src/compute_shuffled_index/mod.rs new file mode 100644 index 000000000..742f4bc34 --- /dev/null +++ b/casper-finality-proofs/src/compute_shuffled_index/mod.rs @@ -0,0 +1,3 @@ +pub mod circuit; + +mod helpers; \ No newline at end of file diff --git a/casper-finality-proofs/src/lib.rs b/casper-finality-proofs/src/lib.rs index 80856a787..32a48b8ee 100644 --- a/casper-finality-proofs/src/lib.rs +++ b/casper-finality-proofs/src/lib.rs @@ -1,3 +1,4 @@ +pub mod compute_shuffled_index; pub mod constants; pub mod test_engine; mod types; diff --git a/casper-finality-proofs/src/test_engine/types/compute_shuffled_index_data.rs b/casper-finality-proofs/src/test_engine/types/compute_shuffled_index_data.rs new file mode 100644 index 000000000..c6ebe7dfe --- /dev/null +++ b/casper-finality-proofs/src/test_engine/types/compute_shuffled_index_data.rs @@ -0,0 +1,10 @@ +use core::fmt::Debug; +use ethers::types::H256; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct TestData { + pub count: u64, + pub seed: H256, + pub mapping: Vec, +} diff --git a/casper-finality-proofs/src/test_engine/types/mod.rs b/casper-finality-proofs/src/test_engine/types/mod.rs index e69de29bb..36decff0c 100644 --- a/casper-finality-proofs/src/test_engine/types/mod.rs +++ b/casper-finality-proofs/src/test_engine/types/mod.rs @@ -0,0 +1 @@ +pub mod compute_shuffled_index_data; diff --git a/casper-finality-proofs/src/test_engine/utils/setup.rs b/casper-finality-proofs/src/test_engine/utils/setup.rs index 39b74eb5d..f8a368561 100644 --- a/casper-finality-proofs/src/test_engine/utils/setup.rs +++ b/casper-finality-proofs/src/test_engine/utils/setup.rs @@ -1,4 +1,10 @@ use super::test_engine::TestCase; +use crate::test_engine::wrappers::compute_shuffled_index::wrapper_mainnet::{ + wrapper as wrapper_mainnet, MAINNET_CIRCUIT as circuit_mainnet, +}; +use crate::test_engine::wrappers::compute_shuffled_index::wrapper_minimal::{ + wrapper as wrapper_minimal, MINIMAL_CIRCUIT as circuit_minimal, +}; use crate::test_engine::wrappers::wrapper_weigh_justification_and_finalization::{ wrapper as wrapper_weigh_justification_and_finalization, CIRCUIT as circuit_weigh_justification_and_finalization, @@ -8,6 +14,8 @@ use strum::{Display, EnumString}; #[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, EnumString, Display)] pub enum TestWrappers { + WrapperComputeShuffledIndexConsensusMainnet, + WrapperComputeShuffledIndexConsensusMinimal, WrapperWeighJustificationAndFinalizationConsensusMainnet, } @@ -26,6 +34,18 @@ pub fn map_test_to_wrapper( wrapper_weigh_justification_and_finalization(path, should_assert) }), ), + TestWrappers::WrapperComputeShuffledIndexConsensusMainnet => ( + Box::new(|| { + Lazy::force(&circuit_mainnet); + }), + Box::new(|path, should_assert| wrapper_mainnet(&path, should_assert)), + ), + TestWrappers::WrapperComputeShuffledIndexConsensusMinimal => ( + Box::new(|| { + Lazy::force(&circuit_minimal); + }), + Box::new(|path, should_assert| wrapper_minimal(&path, should_assert)), + ), } } @@ -36,6 +56,16 @@ pub fn init_tests() -> Vec { "../vendor/consensus-spec-tests/tests/mainnet/capella/epoch_processing/justification_and_finalization/pyspec_tests/".to_string(), true, )); + tests.push(TestCase::new( + TestWrappers::WrapperComputeShuffledIndexConsensusMainnet, + "../vendor/consensus-spec-tests/tests/mainnet/phase0/shuffling/core/shuffle".to_string(), + true, + )); + tests.push(TestCase::new( + TestWrappers::WrapperComputeShuffledIndexConsensusMinimal, + "../vendor/consensus-spec-tests/tests/minimal/phase0/shuffling/core/shuffle".to_string(), + true, + )); tests } diff --git a/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/mod.rs b/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/mod.rs new file mode 100644 index 000000000..b0c547f0a --- /dev/null +++ b/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/mod.rs @@ -0,0 +1,2 @@ +pub mod wrapper_mainnet; +pub mod wrapper_minimal; \ No newline at end of file diff --git a/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_mainnet.rs b/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_mainnet.rs new file mode 100644 index 000000000..1346df496 --- /dev/null +++ b/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_mainnet.rs @@ -0,0 +1,39 @@ +use crate::assert_equal; +use crate::compute_shuffled_index::circuit::define; +use crate::test_engine::types::compute_shuffled_index_data::TestData; +use crate::test_engine::utils::parsers::parse_file::read_fixture; +use once_cell::sync::Lazy; +use plonky2x::backend::circuit::MockCircuitBuild; +use plonky2x::prelude::{Bytes32Variable, U64Variable}; +use plonky2x::prelude::{CircuitBuilder, DefaultParameters}; + +// Singleton-like pattern +pub static MAINNET_CIRCUIT: Lazy> = Lazy::new(|| { + let mut builder = CircuitBuilder::::new(); + define(&mut builder, 90); + builder.mock_build() +}); + +pub fn wrapper(path: &str, should_assert: bool) -> Result { + let json_data: TestData = read_fixture::(path); + + let mut result_indices: Vec = Vec::new(); + + for i in 0..json_data.count { + let mut input = MAINNET_CIRCUIT.input(); + + input.write::(i); + input.write::(json_data.count); + input.write::(json_data.seed); + + let (_witness, mut _output) = MAINNET_CIRCUIT.mock_prove(&input); + let shuffled_index_res = _output.read::(); + if should_assert { + assert_equal!(json_data.mapping[i as usize], shuffled_index_res); + } + + result_indices.push(shuffled_index_res); + } + + Ok(format!("{:?}", result_indices)) +} diff --git a/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_minimal.rs b/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_minimal.rs new file mode 100644 index 000000000..3a0a0e3b1 --- /dev/null +++ b/casper-finality-proofs/src/test_engine/wrappers/compute_shuffled_index/wrapper_minimal.rs @@ -0,0 +1,39 @@ +use crate::assert_equal; +use crate::compute_shuffled_index::circuit::define; +use crate::test_engine::types::compute_shuffled_index_data::TestData; +use crate::test_engine::utils::parsers::parse_file::read_fixture; +use once_cell::sync::Lazy; +use plonky2x::backend::circuit::MockCircuitBuild; +use plonky2x::prelude::{Bytes32Variable, U64Variable}; +use plonky2x::prelude::{CircuitBuilder, DefaultParameters}; + +// Singleton-like pattern +pub static MINIMAL_CIRCUIT: Lazy> = Lazy::new(|| { + let mut builder = CircuitBuilder::::new(); + define(&mut builder, 10); + builder.mock_build() +}); + +pub fn wrapper(path: &str, should_assert: bool) -> Result { + let json_data: TestData = read_fixture::(path); + + let mut result_indices: Vec = Vec::new(); + + for i in 0..json_data.count { + let mut input = MINIMAL_CIRCUIT.input(); + + input.write::(i); + input.write::(json_data.count); + input.write::(json_data.seed); + + let (_witness, mut _output) = MINIMAL_CIRCUIT.mock_prove(&input); + let shuffled_index_res = _output.read::(); + if should_assert { + assert_equal!(json_data.mapping[i as usize], shuffled_index_res); + } + + result_indices.push(shuffled_index_res); + } + + Ok(format!("{:?}", result_indices)) +} diff --git a/casper-finality-proofs/src/test_engine/wrappers/mod.rs b/casper-finality-proofs/src/test_engine/wrappers/mod.rs index 19cca2a1c..930e49e35 100644 --- a/casper-finality-proofs/src/test_engine/wrappers/mod.rs +++ b/casper-finality-proofs/src/test_engine/wrappers/mod.rs @@ -1 +1,2 @@ pub mod wrapper_weigh_justification_and_finalization; +pub mod compute_shuffled_index; \ No newline at end of file diff --git a/casper-finality-proofs/src/utils/bits.rs b/casper-finality-proofs/src/utils/bits.rs deleted file mode 100644 index 16c3d658f..000000000 --- a/casper-finality-proofs/src/utils/bits.rs +++ /dev/null @@ -1,19 +0,0 @@ -use plonky2x::prelude::{BoolVariable, CircuitBuilder, PlonkParameters}; -use std::cmp::min; - -pub fn shift_right, const D: usize>( - builder: &mut CircuitBuilder, - bits: &[BoolVariable], - shift_count: usize, -) -> Vec { - let mut new_bits = bits.to_vec(); - for i in shift_count..bits.len() { - new_bits[i] = bits[i - shift_count]; - } - - for i in 0..min(shift_count, bits.len()) { - new_bits[i] = builder._false(); - } - - new_bits -} diff --git a/casper-finality-proofs/src/utils/mod.rs b/casper-finality-proofs/src/utils/mod.rs index b50fbec22..cbf0971ef 100644 --- a/casper-finality-proofs/src/utils/mod.rs +++ b/casper-finality-proofs/src/utils/mod.rs @@ -1,2 +1 @@ -pub mod bits; -pub mod plonky2x_extensions; +pub mod plonky2x_extensions; \ No newline at end of file diff --git a/casper-finality-proofs/src/utils/plonky2x_extensions.rs b/casper-finality-proofs/src/utils/plonky2x_extensions.rs index f931ae14c..9701e537f 100644 --- a/casper-finality-proofs/src/utils/plonky2x_extensions.rs +++ b/casper-finality-proofs/src/utils/plonky2x_extensions.rs @@ -1,4 +1,7 @@ -use plonky2x::prelude::{BoolVariable, CircuitBuilder, PlonkParameters}; +use plonky2::field::types::Field; +use plonky2::iop::target::BoolTarget; +use plonky2x::prelude::{BoolVariable, CircuitBuilder, PlonkParameters, U64Variable, Variable}; +use std::cmp::min; pub fn assert_is_true, const D: usize>( builder: &mut CircuitBuilder, @@ -15,3 +18,63 @@ pub fn assert_is_false, const D: usize>( let _false = builder._false(); builder.assert_is_equal(condition, _false); } + +/// Returns the little endian representation of bits +pub fn bits_to_variable, const D: usize>( + builder: &mut CircuitBuilder, + bits: &[BoolVariable], +) -> Variable { + let const_2: Variable = builder.constant(L::Field::from_canonical_usize(2)); + let mut power_of_2 = builder.constant(L::Field::from_canonical_usize(1)); + let mut result = builder.constant(L::Field::from_canonical_usize(0)); + for i in 0..bits.len() { + let addend = builder.mul(bits[i].variable, power_of_2); + result = builder.add(addend, result); + power_of_2 = builder.mul(const_2, power_of_2); + } + + result +} + +/// Exponentiate `base` with `exponent`, given its bits in little-endian. +pub fn exp_from_bits, const D: usize>( + builder: &mut CircuitBuilder, + base: Variable, + exponent_bits: &[BoolVariable], +) -> Variable { + Variable( + builder.api.exp_from_bits( + base.0, + exponent_bits + .into_iter() + .map(|x| BoolTarget::new_unsafe(x.variable.0)), + ), + ) +} + +/// Returns the greater one of the two arguments +pub fn max, const D: usize>( + builder: &mut CircuitBuilder, + lhs: U64Variable, + rhs: U64Variable, +) -> U64Variable { + let lhs_lte_rhs = builder.lte(lhs, rhs); + builder.select(lhs_lte_rhs, rhs, lhs) +} + +pub fn shift_right, const D: usize>( + builder: &mut CircuitBuilder, + bits: &[BoolVariable], + shift_count: usize, +) -> Vec { + let mut new_bits = bits.to_vec(); + for i in shift_count..bits.len() { + new_bits[i] = bits[i - shift_count]; + } + + for i in 0..min(shift_count, bits.len()) { + new_bits[i] = builder._false(); + } + + new_bits +} diff --git a/casper-finality-proofs/src/weigh_justification_and_finalization/justification_bits.rs b/casper-finality-proofs/src/weigh_justification_and_finalization/justification_bits.rs index 7c9f6cd95..1395f46bc 100644 --- a/casper-finality-proofs/src/weigh_justification_and_finalization/justification_bits.rs +++ b/casper-finality-proofs/src/weigh_justification_and_finalization/justification_bits.rs @@ -7,7 +7,7 @@ use plonky2x::{ }, }; -use crate::utils::bits::shift_right; +use crate::utils::plonky2x_extensions::shift_right; #[derive(Debug, Clone, CircuitVariable)] #[value_name(JustificationBitsValue)]