Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

chore!: refactor ToRadix to ToRadixLe and ToRadixBe #58

Merged
merged 20 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions acir/src/circuit/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ pub enum Directive {
bit_size: u32,
},

//decomposition of a: a=\sum b[i]*radix^i where b is an array of witnesses < radix
//decomposition of a: a=\sum b[i]*radix^i where b is an array of witnesses < radix in either little endian or big endian form
ToRadix {
a: Expression,
b: Vec<Witness>,
radix: u32,
is_little_endian: bool,
},

// Sort directive, using a sorting network
Expand Down Expand Up @@ -122,13 +123,19 @@ impl Directive {
write_u32(&mut writer, r.witness_index())?;
write_u32(&mut writer, *bit_size)?;
}
Directive::ToRadix { a, b, radix } => {
Directive::ToRadix {
a,
b,
radix,
is_little_endian,
} => {
a.write(&mut writer)?;
write_u32(&mut writer, b.len() as u32)?;
for bit in b {
write_u32(&mut writer, bit.witness_index())?;
}
write_u32(&mut writer, *radix)?;
write_u32(&mut writer, *is_little_endian as u32)?;
}
Directive::PermutationSort {
inputs: a,
Expand Down Expand Up @@ -222,8 +229,14 @@ impl Directive {
}

let radix = read_u32(&mut reader)?;
let is_little_endian = read_u32(&mut reader)?;

Ok(Directive::ToRadix { a, b, radix })
Ok(Directive::ToRadix {
a,
b,
radix,
is_little_endian: is_little_endian == 1,
})
}
6 => {
let tuple = read_u32(&mut reader)?;
Expand Down Expand Up @@ -314,10 +327,18 @@ fn serialization_roundtrip() {
bit_size: 32,
};

let to_radix = Directive::ToRadix {
let to_radix_le = Directive::ToRadix {
a: Expression::default(),
b: vec![Witness(1u32), Witness(2u32), Witness(3u32), Witness(4u32)],
radix: 4,
is_little_endian: true,
};

let to_radix_be = Directive::ToRadix {
a: Expression::default(),
b: vec![Witness(1u32), Witness(2u32), Witness(3u32), Witness(4u32)],
radix: 4,
is_little_endian: false,
};

let directives = vec![
Expand All @@ -326,7 +347,8 @@ fn serialization_roundtrip() {
quotient_predicate,
truncate,
odd_range,
to_radix,
to_radix_le,
to_radix_be,
];

for directive in directives {
Expand Down
10 changes: 8 additions & 2 deletions acir/src/circuit/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,22 @@ impl std::fmt::Display for Opcode {
)
}
Opcode::BlackBoxFuncCall(g) => write!(f, "{g}"),
Opcode::Directive(Directive::ToRadix { a, b, radix: _ }) => {
Opcode::Directive(Directive::ToRadix {
a,
b,
radix: _,
is_little_endian,
}) => {
write!(f, "DIR::TORADIX ")?;
write!(
f,
// TODO (Note): this assumes that the decomposed bits have contiguous witness indices
// This should be the case, however, we can also have a function which checks this
"(_{}, [_{}..._{}])",
"(_{}, [_{}..._{}], endianness: {})",
a,
b.first().unwrap().witness_index(),
b.last().unwrap().witness_index(),
if *is_little_endian { "little" } else { "big" }
)
}
Opcode::Directive(Directive::PermutationSort {
Expand Down
2 changes: 2 additions & 0 deletions acvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum OpcodeNotSolvable {
MissingAssignment(u32),
#[error("expression has too many unknowns {0}")]
ExpressionHasTooManyUnknowns(Expression),
#[error("compiler error: unreachable code")]
Ethan-000 marked this conversation as resolved.
Show resolved Hide resolved
UnreachableCode,
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(PartialEq, Eq, Debug, Error)]
Expand Down
23 changes: 23 additions & 0 deletions acvm/src/pwg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,26 @@ pub fn get_value(

Ok(result)
}

// Inserts `value` into the initial witness map
// under the key of `witness`.
// Returns an error, if there was already a value in the map
// which does not match the value that one is about to insert
fn insert_value(
witness: &Witness,
value_to_insert: FieldElement,
initial_witness: &mut BTreeMap<Witness, FieldElement>,
) -> Result<(), OpcodeResolutionError> {
let optional_old_value = initial_witness.insert(*witness, value_to_insert);

let old_value = match optional_old_value {
Some(old_value) => old_value,
None => return Ok(()),
};

if old_value != value_to_insert {
return Err(OpcodeResolutionError::UnsatisfiedConstrain);
}

Ok(())
}
69 changes: 53 additions & 16 deletions acvm/src/pwg/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use acir::{
use num_bigint::BigUint;
use num_traits::{One, Zero};

use crate::OpcodeResolutionError;
use crate::{OpcodeNotSolvable, OpcodeResolutionError};

use super::{get_value, sorting::route, witness_to_value};
use super::{get_value, insert_value, sorting::route, witness_to_value};

pub fn solve_directives(
initial_witness: &mut BTreeMap<Witness, FieldElement>,
Expand Down Expand Up @@ -84,21 +84,58 @@ pub fn solve_directives(

Ok(())
}
Directive::ToRadix { a, b, radix } => {
let val_a = get_value(a, initial_witness)?;
Directive::ToRadix {
a,
b,
radix,
is_little_endian,
} => {
let value_a = get_value(a, initial_witness)?;

let a_big = BigUint::from_bytes_be(&val_a.to_be_bytes());
let a_dec = a_big.to_radix_le(*radix);
if b.len() < a_dec.len() {
return Err(OpcodeResolutionError::UnsatisfiedConstrain);
}
for i in 0..b.len() {
let v = if i < a_dec.len() {
FieldElement::from_be_bytes_reduce(&[a_dec[i]])
} else {
FieldElement::zero()
};
insert_witness(b[i], v, initial_witness)?;
let big_integer = BigUint::from_bytes_be(&value_a.to_be_bytes());

if *is_little_endian {
// Decompose the integer into its radix digits in little endian form.
let decomposed_integer = big_integer.to_radix_le(*radix);

if b.len() < decomposed_integer.len() {
return Err(OpcodeResolutionError::UnsatisfiedConstrain);
}

for (i, witness) in b.iter().enumerate() {
// Fetch the `i'th` digit from the decomposed integer list
// and convert it to a field element.
// If it is not available, which can happen when the decomposed integer
// list is shorter than the witness list, we return 0.
let value = match decomposed_integer.get(i) {
Some(digit) => FieldElement::from_be_bytes_reduce(&[*digit]),
None => FieldElement::zero(),
};

insert_value(witness, value, initial_witness)?
}
} else {
// Decompose the integer into its radix digits in big endian form.
let decomposed_integer = big_integer.to_radix_be(*radix);

// if it is big endian and the decompoased integer list is shorter
// than the witness list, pad the extra part with 0 first then
// add the decompsed interger list to the witness list.
let padding_len = b.len() - decomposed_integer.len();
let mut value = FieldElement::zero();
for (i, witness) in b.iter().enumerate() {
if i >= padding_len {
value = match decomposed_integer.get(i - padding_len) {
Some(digit) => FieldElement::from_be_bytes_reduce(&[*digit]),
None => {
return Err(OpcodeResolutionError::OpcodeNotSolvable(
OpcodeNotSolvable::UnreachableCode,
))
}
};
}
insert_value(witness, value, initial_witness)?
}
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions stdlib/src/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub(crate) fn bit_decomposition(
a: gate.clone(),
b: bit_vector.clone(),
radix: 2,
is_little_endian: true,
}));

// Now apply constraints to the bits such that they are the bit decomposition
Expand Down