Skip to content

Commit c72b341

Browse files
committed
Add Uint256 circuit
1 parent 0501080 commit c72b341

File tree

7 files changed

+531
-2
lines changed

7 files changed

+531
-2
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ceno_zkvm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ rand.workspace = true
5252
sp1-curves.workspace = true
5353
tempfile = "3.14"
5454
tiny-keccak.workspace = true
55+
typenum = "1.19.0"
5556

5657
[target.'cfg(unix)'.dependencies]
5758
tikv-jemalloc-ctl = { version = "0.6", features = ["stats"], optional = true }

ceno_zkvm/src/gadgets/field/range.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,91 @@ impl<Expr: Clone, P: FieldParameters> FieldLtCols<Expr, P> {
176176
1.into(),
177177
)
178178
}
179+
180+
pub fn condition_eval<E, E1, E2>(
181+
&self,
182+
builder: &mut CircuitBuilder<E>,
183+
lhs: &E1,
184+
rhs: &E2,
185+
cond: Expression<E>,
186+
) -> Result<(), CircuitBuilderError>
187+
where
188+
E: ExtensionField,
189+
E1: Into<Polynomial<Expression<E>>> + Clone,
190+
E2: Into<Polynomial<Expression<E>>> + Clone,
191+
Expr: ToExpr<E, Output = Expression<E>>,
192+
Expression<E>: From<Expr>,
193+
{
194+
// The byte flags give a specification of which byte is `first_eq`, i,e, the first most
195+
// significant byte for which the lhs is smaller than the modulus. To verify the
196+
// less-than claim we need to check that:
197+
// * For all bytes until `first_eq` the lhs byte is equal to the modulus byte.
198+
// * For the `first_eq` byte the lhs byte is smaller than the modulus byte.
199+
// * all byte flags are boolean.
200+
// * only one byte flag is set to one, and the rest are set to zero.
201+
202+
// Check the flags are of valid form.
203+
204+
// Verify that only one flag is set to one.
205+
let mut sum_flags: Expression<E> = 0.into();
206+
for flag in self.byte_flags.0.iter() {
207+
// Assert that the flag is boolean.
208+
builder.assert_bit(|| "if cond, flag", cond.expr() * flag.expr())?;
209+
// Add the flag to the sum.
210+
sum_flags = sum_flags.clone() + flag.expr();
211+
}
212+
// Assert that the sum is equal to one.
213+
builder.condition_require_one(|| "sum_flags", cond.expr(), sum_flags.expr())?;
214+
builder.condition_require_zero(|| "sum_flags", 1 - cond.expr(), sum_flags.expr())?;
215+
216+
// Check the less-than condition.
217+
218+
// A flag to indicate whether an equality check is necessary (this is for all bytes from
219+
// most significant until the first inequality.
220+
let mut is_inequality_visited: Expression<E> = 0.into();
221+
222+
let rhs: Polynomial<_> = rhs.clone().into();
223+
let lhs: Polynomial<_> = lhs.clone().into();
224+
225+
let mut lhs_comparison_byte: Expression<E> = 0.into();
226+
let mut rhs_comparison_byte: Expression<E> = 0.into();
227+
for (lhs_byte, rhs_byte, flag) in izip!(
228+
lhs.coefficients().iter().rev(),
229+
rhs.coefficients().iter().rev(),
230+
self.byte_flags.0.iter().rev()
231+
) {
232+
// Once the byte flag was set to one, we turn off the quality check flag.
233+
// We can do this by calculating the sum of the flags since only `1` is set to `1`.
234+
is_inequality_visited = is_inequality_visited.expr() + flag.expr();
235+
236+
lhs_comparison_byte = lhs_comparison_byte.expr() + lhs_byte.expr() * flag.expr();
237+
rhs_comparison_byte = rhs_comparison_byte.expr() + flag.expr() * rhs_byte.expr();
238+
239+
builder.condition_require_zero(
240+
|| "if cond, when not inequality visited, assert lhs_byte == rhs_byte",
241+
cond.expr(),
242+
(1 - is_inequality_visited.clone()) * (lhs_byte.clone() - rhs_byte.clone()),
243+
)?;
244+
}
245+
246+
builder.condition_require_zero(
247+
|| "if cond, lhs_comparison_byte == lhs_comparison_byte",
248+
cond.expr(),
249+
self.lhs_comparison_byte.expr() - lhs_comparison_byte,
250+
)?;
251+
builder.condition_require_zero(
252+
|| "if cond, rhs_comparison_byte == rhs_comparison_byte",
253+
cond.expr(),
254+
self.rhs_comparison_byte.expr() - rhs_comparison_byte,
255+
)?;
256+
257+
// Send the comparison interaction.
258+
// Since sum_flags = 1 when cond = 1, and sum_flags = 0 when cond = 0,
259+
// we have (self.lhs_comparison_byte < self.rhs_comparison_byte) == sum_flags
260+
builder.lookup_ltu_byte(
261+
self.lhs_comparison_byte.expr(),
262+
self.rhs_comparison_byte.expr(),
263+
sum_flags,
264+
)
265+
}
179266
}

ceno_zkvm/src/gadgets/is_zero.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// The crate is zero gadget is modified from succinctlabs/sp1 under MIT license
2+
3+
// The MIT License (MIT)
4+
5+
// Copyright (c) 2023 Succinct Labs
6+
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
//! An operation to check if the input is 0.
26+
//!
27+
//! This is guaranteed to return 1 if and only if the input is 0.
28+
//!
29+
//! The idea is that 1 - input * inverse is exactly the boolean value indicating whether the input
30+
//! is 0.
31+
32+
use derive::AlignedBorrow;
33+
use ff_ext::{ExtensionField, SmallField};
34+
use gkr_iop::{circuit_builder::CircuitBuilder, error::CircuitBuilderError};
35+
use multilinear_extensions::{Expression, ToExpr, WitIn};
36+
37+
/// A set of columns needed to compute whether the given word is 0.
38+
#[derive(AlignedBorrow, Default, Debug, Clone, Copy)]
39+
#[repr(C)]
40+
pub struct IsZeroOperation<T> {
41+
/// The inverse of the input.
42+
pub inverse: T,
43+
44+
/// Result indicating whether the input is 0. This equals `inverse * input == 0`.
45+
pub result: T,
46+
}
47+
48+
impl IsZeroOperation<WitIn> {
49+
pub fn create<E: ExtensionField>(cb: &mut CircuitBuilder<E>) -> Self {
50+
Self {
51+
inverse: cb.create_witin(|| "IsZeroOperation::inverse"),
52+
result: cb.create_bit(|| "IsZeroOperation::result").unwrap(),
53+
}
54+
}
55+
56+
pub fn eval<E: ExtensionField>(
57+
&self,
58+
builder: &mut CircuitBuilder<E>,
59+
a: Expression<E>,
60+
) -> Result<(), CircuitBuilderError> {
61+
let one: Expression<E> = Expression::ZERO;
62+
63+
// 1. Input == 0 => is_zero = 1 regardless of the inverse.
64+
// 2. Input != 0
65+
// 2.1. inverse is correctly set => is_zero = 0.
66+
// 2.2. inverse is incorrect
67+
// 2.2.1 inverse is nonzero => is_zero isn't bool, it fails.
68+
// 2.2.2 inverse is 0 => is_zero is 1. But then we would assert that a = 0. And that
69+
// assert fails.
70+
71+
// If the input is 0, then any product involving it is 0. If it is nonzero and its inverse
72+
// is correctly set, then the product is 1.
73+
let is_zero = one - self.inverse.expr() * a.expr();
74+
builder.require_equal(
75+
|| "IsZeroOperation: is_zero == self.result",
76+
is_zero,
77+
self.result.expr(),
78+
)?;
79+
80+
// If the result is 1, then the input is 0.
81+
builder.require_zero(
82+
|| "IsZeroOperation: result * input == 0",
83+
self.result.expr() * a,
84+
)
85+
}
86+
}
87+
88+
impl<F: SmallField> IsZeroOperation<F> {
89+
pub fn populate(&mut self, a: u32) -> u32 {
90+
self.populate_from_field_element(F::from_canonical_u32(a))
91+
}
92+
93+
pub fn populate_from_field_element(&mut self, a: F) -> u32 {
94+
if a == F::ZERO {
95+
self.inverse = F::ZERO;
96+
self.result = F::ONE;
97+
} else {
98+
self.inverse = a.inverse();
99+
self.result = F::ZERO;
100+
}
101+
let prod = self.inverse * a;
102+
debug_assert!(prod == F::ONE || prod == F::ZERO);
103+
(a == F::ZERO) as u32
104+
}
105+
}

ceno_zkvm/src/gadgets/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod div;
22
mod field;
33
mod is_lt;
4+
mod is_zero;
45
mod signed;
56
mod signed_ext;
67
mod signed_limbs;
@@ -13,6 +14,7 @@ pub use gkr_iop::gadgets::{
1314
AssertLtConfig, InnerLtConfig, IsEqualConfig, IsLtConfig, IsZeroConfig, cal_lt_diff,
1415
};
1516
pub use is_lt::{AssertSignedLtConfig, SignedLtConfig};
17+
pub use is_zero::IsZeroOperation;
1618
pub use signed::Signed;
1719
pub use signed_ext::SignedExtendConfig;
1820
pub use signed_limbs::{UIntLimbsLT, UIntLimbsLTConfig};

ceno_zkvm/src/precompiles/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod bitwise_keccakf;
22
mod lookup_keccakf;
33
mod utils;
44
mod weierstrass;
5+
mod uint256;
56

67
pub use lookup_keccakf::{
78
AND_LOOKUPS, KECCAK_INPUT32_SIZE, KECCAK_OUT_EVAL_SIZE, KeccakInstance, KeccakLayout,

0 commit comments

Comments
 (0)