diff --git a/examples/fixture/asm/kimchi/generic_builtin_bits.asm b/examples/fixture/asm/kimchi/generic_builtin_bits.asm index 87ceb2cce..5aabaad0a 100644 --- a/examples/fixture/asm/kimchi/generic_builtin_bits.asm +++ b/examples/fixture/asm/kimchi/generic_builtin_bits.asm @@ -11,9 +11,6 @@ DoubleGeneric<1,0,-1,0,1> DoubleGeneric<1,-1> DoubleGeneric<0,0,-1,1> DoubleGeneric<1> -DoubleGeneric<1,0,-1> -DoubleGeneric<1,1> -DoubleGeneric<1,0,-1,0,1> DoubleGeneric<1,1> DoubleGeneric<1,1,-1> DoubleGeneric<0,0,-1,1> @@ -22,9 +19,6 @@ DoubleGeneric<1,0,-1,0,1> DoubleGeneric<1,-1> DoubleGeneric<0,0,-1,1> DoubleGeneric<1> -DoubleGeneric<1,0,-1> -DoubleGeneric<1,1> -DoubleGeneric<1,0,-1,0,1> DoubleGeneric<1,1> DoubleGeneric<1,1,-1> DoubleGeneric<0,0,-1,1> @@ -36,9 +30,6 @@ DoubleGeneric<1> DoubleGeneric<1,0,-1> DoubleGeneric<1,1> DoubleGeneric<1,0,-1,0,1> -DoubleGeneric<1,0,-1> -DoubleGeneric<1,1> -DoubleGeneric<1,0,-1,0,1> DoubleGeneric<2,0,-1> DoubleGeneric<1,1> DoubleGeneric<1,0,-1,0,1> @@ -68,52 +59,46 @@ DoubleGeneric<1,0,-1,0,1> DoubleGeneric<1,1,-1> DoubleGeneric<1,-1> DoubleGeneric<1,0,0,0,-2> -(0,0) -> (46,1) -> (65,1) -> (66,0) -(1,0) -> (3,0) -> (14,0) -> (25,0) +(0,0) -> (37,1) -> (56,1) -> (57,0) +(1,0) -> (3,0) -> (11,0) -> (19,0) (2,1) -> (3,1) (3,2) -> (4,1) -> (8,1) (4,2) -> (7,0) -(5,0) -> (8,0) -> (10,0) -> (11,0) +(5,0) -> (8,0) -> (26,0) -> (27,0) -> (38,0) -> (45,0) -> (46,0) (5,1) -> (6,0) (6,2) -> (7,1) (8,2) -> (9,0) -(10,2) -> (35,0) -> (36,0) -> (47,0) -> (54,0) -> (55,0) -(11,1) -> (12,0) -(13,1) -> (14,1) -(14,2) -> (15,1) -> (19,1) -(15,2) -> (18,0) -(16,0) -> (19,0) -> (21,0) -> (22,0) -(16,1) -> (17,0) -(17,2) -> (18,1) -(19,2) -> (20,0) -(21,2) -> (38,0) -> (39,0) -> (50,0) -> (57,0) -> (58,0) -(22,1) -> (23,0) -(24,1) -> (25,1) -(25,2) -> (26,1) -> (30,1) -(26,2) -> (29,0) -(27,0) -> (30,0) -> (32,0) -> (33,0) +(10,1) -> (11,1) +(11,2) -> (12,1) -> (16,1) +(12,2) -> (15,0) +(13,0) -> (16,0) -> (29,0) -> (30,0) -> (41,0) -> (48,0) -> (49,0) +(13,1) -> (14,0) +(14,2) -> (15,1) +(16,2) -> (17,0) +(18,1) -> (19,1) +(19,2) -> (20,1) -> (24,1) +(20,2) -> (23,0) +(21,0) -> (24,0) -> (33,0) -> (34,0) -> (42,0) -> (52,0) -> (53,0) +(21,1) -> (22,0) +(22,2) -> (23,1) +(24,2) -> (25,0) +(26,2) -> (32,0) (27,1) -> (28,0) -(28,2) -> (29,1) -(30,2) -> (31,0) -(32,2) -> (42,0) -> (43,0) -> (51,0) -> (61,0) -> (62,0) -(33,1) -> (34,0) -(35,2) -> (41,0) -(36,1) -> (37,0) -(38,2) -> (41,1) -(39,1) -> (40,0) -(41,2) -> (45,0) -(42,2) -> (45,1) -(43,1) -> (44,0) -(45,2) -> (46,0) -(47,1) -> (48,0) -(48,2) -> (49,0) -(51,1) -> (52,0) -(52,2) -> (53,0) -(54,2) -> (60,0) -(55,1) -> (56,0) -(57,2) -> (60,1) -(58,1) -> (59,0) -(60,2) -> (64,0) -(61,2) -> (64,1) -(62,1) -> (63,0) -(64,2) -> (65,0) +(29,2) -> (32,1) +(30,1) -> (31,0) +(32,2) -> (36,0) +(33,2) -> (36,1) +(34,1) -> (35,0) +(36,2) -> (37,0) +(38,1) -> (39,0) +(39,2) -> (40,0) +(42,1) -> (43,0) +(43,2) -> (44,0) +(45,2) -> (51,0) +(46,1) -> (47,0) +(48,2) -> (51,1) +(49,1) -> (50,0) +(51,2) -> (55,0) +(52,2) -> (55,1) +(53,1) -> (54,0) +(55,2) -> (56,0) diff --git a/src/stdlib/native/bits/lib.no b/src/stdlib/native/bits/lib.no index 1d7097eb2..0a9778fba 100644 --- a/src/stdlib/native/bits/lib.no +++ b/src/stdlib/native/bits/lib.no @@ -10,6 +10,37 @@ /// hint fn nth_bit(value: Field, const nth: Field) -> Field; +/// A `Field` value representing a bit. +/// Use `Bit.new` to create with a range-checked value. +struct Bit { + inner: Field, +} + +/// Creates a `Bit` without any range check on `val`. +fn Bit.new_unchecked(val: Field) -> Bit { + return Bit { + inner: val + }; +} + +/// Creates a `Bit` with a range check on `val` so that it must be `0` or `1`. +/// +/// # Panics +/// - `val` is neither `0` nor `1`. +fn Bit.new(val: Field) -> Bit { + assert_eq(val * (val - 1), 0); + + return Bit.new_unchecked(val); +} + +/// Converts to a boolean. +/// +/// # Returns +/// - `Bool`: `true` if value is `1` and `false` otherwise. +fn Bit.to_bool(self) -> Bool { + return self.inner == 1; +} + /// Converts an array of boolean values (`bits`) into a `Field` value. /// /// # Parameters @@ -56,21 +87,16 @@ fn from_bits(bits: [Bool; LEN]) -> Field { /// - The function asserts that `from_bits(bits)` equals `value`, ensuring the conversion is correct. fn to_bits(const LEN: Field, value: Field) -> [Bool; LEN] { let mut bits = [false; LEN]; - let mut lc1 = 0; - let mut e2 = 1; - - // TODO: ITE should allow literals. - let true_val = true; - let false_val = false; for index in 0..LEN { let bit_num = unsafe nth_bit(value, index); - // constraint the bit values to booleans - bits[index] = if bit_num == 1 { true_val } else { false_val }; + // constrain the bit values to booleans + let bit = Bit.new_unchecked(bit_num); + bits[index] = bit.to_bool(); } - // constraint the accumulative contributions of bits to be equal to the value + // constrain the accumulative contributions of bits to be equal to the value assert_eq(from_bits(bits), value); return bits; diff --git a/src/tests/stdlib/bits/bit_checked.no b/src/tests/stdlib/bits/bit_checked.no new file mode 100644 index 000000000..c94679085 --- /dev/null +++ b/src/tests/stdlib/bits/bit_checked.no @@ -0,0 +1,7 @@ +use std::bits; + +fn main(pub val: Field) -> Bool { + let bit = bits::Bit.new(val); + + return bit.to_bool(); +} diff --git a/src/tests/stdlib/bits/bit_unchecked.no b/src/tests/stdlib/bits/bit_unchecked.no new file mode 100644 index 000000000..a3c67f3a7 --- /dev/null +++ b/src/tests/stdlib/bits/bit_unchecked.no @@ -0,0 +1,7 @@ +use std::bits; + +fn main(pub val: Field) -> Bool { + let bit = bits::Bit.new_unchecked(val); + + return bit.to_bool(); +} diff --git a/src/tests/stdlib/bits/mod.rs b/src/tests/stdlib/bits/mod.rs new file mode 100644 index 000000000..708839e7c --- /dev/null +++ b/src/tests/stdlib/bits/mod.rs @@ -0,0 +1,53 @@ +use crate::error::{self, ErrorKind}; + +use super::test_stdlib; +use error::Result; +use rstest::rstest; + +#[rstest] +#[case(r#"{"val": "0"}"#, vec!["0"])] +#[case(r#"{"val": "1"}"#, vec!["1"])] +fn test_bit_checked(#[case] public_inputs: &str, #[case] expected_output: Vec<&str>) -> Result<()> { + test_stdlib( + "bits/bit_checked.no", + None, + public_inputs, + r#"{}"#, + expected_output, + )?; + + Ok(()) +} + +#[test] +fn test_bit_checked_witness_failure() -> Result<()> { + let public_inputs = r#"{"val": "2"}"#; // should break range check + + let err = test_stdlib("bits/bit_checked.no", None, public_inputs, r#"{}"#, vec![]) + .err() + .expect("expected witness error"); + + assert!(matches!(err.kind, ErrorKind::InvalidWitness(..))); + + Ok(()) +} + +#[rstest] +#[case(r#"{"val": "0"}"#, vec!["0"])] // 0 => false +#[case(r#"{"val": "1"}"#, vec!["1"])] // 1 => true +#[case(r#"{"val": "2"}"#, vec!["0"])] // _ => false +#[case(r#"{"val": "99"}"#, vec!["0"])] +fn test_bit_unchecked( + #[case] public_inputs: &str, + #[case] expected_output: Vec<&str>, +) -> Result<()> { + test_stdlib( + "bits/bit_unchecked.no", + None, + public_inputs, + r#"{}"#, + expected_output, + )?; + + Ok(()) +} diff --git a/src/tests/stdlib/mod.rs b/src/tests/stdlib/mod.rs index 278366d40..e177794ca 100644 --- a/src/tests/stdlib/mod.rs +++ b/src/tests/stdlib/mod.rs @@ -1,3 +1,4 @@ +mod bits; mod comparator; mod mimc; mod multiplexer;