less_than.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use halo2_proofs::{
  19. arithmetic::Field,
  20. circuit::{AssignedCell, Chip, Layouter, Region, Value},
  21. pasta::pallas,
  22. plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, TableColumn},
  23. poly::Rotation,
  24. };
  25. use super::native_range_check::{NativeRangeCheckChip, NativeRangeCheckConfig};
  26. #[derive(Clone, Debug)]
  27. pub struct LessThanConfig<
  28. const WINDOW_SIZE: usize,
  29. const NUM_OF_BITS: usize,
  30. const NUM_OF_WINDOWS: usize,
  31. > {
  32. pub s_lt: Selector,
  33. pub a: Column<Advice>,
  34. pub b: Column<Advice>,
  35. pub a_offset: Column<Advice>,
  36. pub range_a_config: NativeRangeCheckConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>,
  37. pub range_a_offset_config: NativeRangeCheckConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>,
  38. pub k_values_table: TableColumn,
  39. }
  40. #[derive(Clone, Debug)]
  41. pub struct LessThanChip<
  42. const WINDOW_SIZE: usize,
  43. const NUM_OF_BITS: usize,
  44. const NUM_OF_WINDOWS: usize,
  45. > {
  46. config: LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>,
  47. }
  48. impl<const WINDOW_SIZE: usize, const NUM_OF_BITS: usize, const NUM_OF_WINDOWS: usize>
  49. Chip<pallas::Base> for LessThanChip<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>
  50. {
  51. type Config = LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>;
  52. type Loaded = ();
  53. fn config(&self) -> &Self::Config {
  54. &self.config
  55. }
  56. fn loaded(&self) -> &Self::Loaded {
  57. &()
  58. }
  59. }
  60. impl<const WINDOW_SIZE: usize, const NUM_OF_BITS: usize, const NUM_OF_WINDOWS: usize>
  61. LessThanChip<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>
  62. {
  63. pub fn construct(config: LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>) -> Self {
  64. Self { config }
  65. }
  66. pub fn configure(
  67. meta: &mut ConstraintSystem<pallas::Base>,
  68. a: Column<Advice>,
  69. b: Column<Advice>,
  70. a_offset: Column<Advice>,
  71. z1: Column<Advice>,
  72. z2: Column<Advice>,
  73. k_values_table: TableColumn,
  74. ) -> LessThanConfig<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS> {
  75. let s_lt = meta.selector();
  76. meta.enable_equality(a);
  77. meta.enable_equality(b);
  78. meta.enable_equality(a_offset);
  79. meta.enable_equality(z1);
  80. meta.enable_equality(z2);
  81. // configure range check for `a` and `offset`
  82. let range_a_config =
  83. NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::configure(
  84. meta,
  85. z1,
  86. k_values_table,
  87. );
  88. let range_a_offset_config =
  89. NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::configure(
  90. meta,
  91. z2,
  92. k_values_table,
  93. );
  94. let config = LessThanConfig {
  95. s_lt,
  96. a,
  97. b,
  98. a_offset,
  99. range_a_config,
  100. range_a_offset_config,
  101. k_values_table,
  102. };
  103. meta.create_gate("a_offset - 2^m + b - a", |meta| {
  104. let s_lt = meta.query_selector(config.s_lt);
  105. let a = meta.query_advice(config.a, Rotation::cur());
  106. let b = meta.query_advice(config.b, Rotation::cur());
  107. let a_offset = meta.query_advice(config.a_offset, Rotation::cur());
  108. let two_pow_m =
  109. Expression::Constant(pallas::Base::from(2).pow([NUM_OF_BITS as u64, 0, 0, 0]));
  110. // a_offset - 2^m + b - a = 0
  111. vec![s_lt * (a_offset - two_pow_m + b - a)]
  112. });
  113. config
  114. }
  115. pub fn witness_less_than(
  116. &self,
  117. mut layouter: impl Layouter<pallas::Base>,
  118. a: Value<pallas::Base>,
  119. b: Value<pallas::Base>,
  120. offset: usize,
  121. strict: bool,
  122. ) -> Result<(), Error> {
  123. let (a, _, a_offset) = layouter.assign_region(
  124. || "a less than b",
  125. |mut region: Region<'_, pallas::Base>| {
  126. let a = region.assign_advice(|| "a", self.config.a, offset, || a)?;
  127. let b = region.assign_advice(|| "b", self.config.b, offset, || b)?;
  128. let a_offset = self.less_than(region, a.clone(), b.clone(), offset)?;
  129. Ok((a, b, a_offset))
  130. },
  131. )?;
  132. self.less_than_range_check(layouter, a, a_offset, strict)?;
  133. Ok(())
  134. }
  135. /*
  136. pub fn witness_less_than2(
  137. &self,
  138. mut layouter: impl Layouter<pallas::Base>,
  139. a: Value<pallas::Base>,
  140. b: Value<pallas::Base>,
  141. offset: usize,
  142. strict: bool,
  143. ) -> Result<AssignedCell<pallas::Base, pallas::Base>, Error> {
  144. let (a, _, a_offset) = layouter.assign_region(
  145. || "a less than b",
  146. |mut region: Region<'_, pallas::Base>| {
  147. let a = region.assign_advice(|| "a", self.config.a, offset, || a)?;
  148. let b = region.assign_advice(|| "b", self.config.b, offset, || b)?;
  149. let a_offset = self.less_than(region, a.clone(), b.clone(), offset)?;
  150. Ok((a, b, a_offset))
  151. },
  152. )?;
  153. self.less_than_range_check(layouter, a, a_offset.clone(), strict)?;
  154. Ok(a_offset)
  155. }
  156. */
  157. pub fn copy_less_than(
  158. &self,
  159. mut layouter: impl Layouter<pallas::Base>,
  160. a: AssignedCell<pallas::Base, pallas::Base>,
  161. b: AssignedCell<pallas::Base, pallas::Base>,
  162. offset: usize,
  163. strict: bool,
  164. ) -> Result<(), Error> {
  165. let (a, _, a_offset) = layouter.assign_region(
  166. || "a less than b",
  167. |mut region: Region<'_, pallas::Base>| {
  168. let a = a.copy_advice(|| "a", &mut region, self.config.a, offset)?;
  169. let b = b.copy_advice(|| "b", &mut region, self.config.b, offset)?;
  170. let a_offset = self.less_than(region, a.clone(), b.clone(), offset)?;
  171. Ok((a, b, a_offset))
  172. },
  173. )?;
  174. self.less_than_range_check(layouter, a, a_offset, strict)?;
  175. Ok(())
  176. }
  177. pub fn less_than_range_check(
  178. &self,
  179. mut layouter: impl Layouter<pallas::Base>,
  180. a: AssignedCell<pallas::Base, pallas::Base>,
  181. a_offset: AssignedCell<pallas::Base, pallas::Base>,
  182. strict: bool,
  183. ) -> Result<(), Error> {
  184. let range_a_chip =
  185. NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::construct(
  186. self.config.range_a_config.clone(),
  187. );
  188. let range_a_offset_chip =
  189. NativeRangeCheckChip::<WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>::construct(
  190. self.config.range_a_offset_config.clone(),
  191. );
  192. range_a_chip.copy_range_check(layouter.namespace(|| "a copy_range_check"), a, strict)?;
  193. range_a_offset_chip.copy_range_check(
  194. layouter.namespace(|| "a_offset copy_range_check"),
  195. a_offset,
  196. strict,
  197. )?;
  198. Ok(())
  199. }
  200. pub fn less_than(
  201. &self,
  202. mut region: Region<'_, pallas::Base>,
  203. a: AssignedCell<pallas::Base, pallas::Base>,
  204. b: AssignedCell<pallas::Base, pallas::Base>,
  205. offset: usize,
  206. ) -> Result<AssignedCell<pallas::Base, pallas::Base>, Error> {
  207. // enable `less_than` selector
  208. self.config.s_lt.enable(&mut region, offset)?;
  209. // assign `a + offset`
  210. let two_pow_m = pallas::Base::from(2).pow([NUM_OF_BITS as u64, 0, 0, 0]);
  211. let a_offset = a.value().zip(b.value()).map(|(a, b)| *a + (two_pow_m - b));
  212. let a_offset =
  213. region.assign_advice(|| "a_offset", self.config.a_offset, offset, || a_offset)?;
  214. Ok(a_offset)
  215. }
  216. }
  217. #[cfg(test)]
  218. mod tests {
  219. use super::*;
  220. use darkfi_sdk::crypto::pasta_prelude::PrimeField;
  221. use halo2_proofs::{
  222. circuit::{floor_planner, Value},
  223. dev::{CircuitLayout, MockProver},
  224. pasta::pallas,
  225. plonk::Circuit,
  226. };
  227. macro_rules! test_circuit {
  228. ($window_size:expr, $num_bits:expr, $num_windows:expr) => {
  229. #[derive(Default)]
  230. struct LessThanCircuit {
  231. a: Value<pallas::Base>,
  232. b: Value<pallas::Base>,
  233. }
  234. impl Circuit<pallas::Base> for LessThanCircuit {
  235. type Config =
  236. (LessThanConfig<$window_size, $num_bits, $num_windows>, Column<Advice>);
  237. type FloorPlanner = floor_planner::V1;
  238. fn without_witnesses(&self) -> Self {
  239. Self { a: Value::unknown(), b: Value::unknown() }
  240. }
  241. fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
  242. let w = meta.advice_column();
  243. meta.enable_equality(w);
  244. let a = meta.advice_column();
  245. let b = meta.advice_column();
  246. let a_offset = meta.advice_column();
  247. let z1 = meta.advice_column();
  248. let z2 = meta.advice_column();
  249. let k_values_table = meta.lookup_table_column();
  250. let constants = meta.fixed_column();
  251. meta.enable_constant(constants);
  252. (
  253. LessThanChip::<$window_size, $num_bits, $num_windows>::configure(
  254. meta,
  255. a,
  256. b,
  257. a_offset,
  258. z1,
  259. z2,
  260. k_values_table,
  261. ),
  262. w,
  263. )
  264. }
  265. fn synthesize(
  266. &self,
  267. config: Self::Config,
  268. mut layouter: impl Layouter<pallas::Base>,
  269. ) -> Result<(), Error> {
  270. let less_than_chip =
  271. LessThanChip::<$window_size, $num_bits, $num_windows>::construct(
  272. config.0.clone(),
  273. );
  274. NativeRangeCheckChip::<$window_size, $num_bits, $num_windows>::load_k_table(
  275. &mut layouter,
  276. config.0.k_values_table,
  277. )?;
  278. less_than_chip.witness_less_than(
  279. layouter.namespace(|| "a < b"),
  280. self.a,
  281. self.b,
  282. 0,
  283. true,
  284. )?;
  285. Ok(())
  286. }
  287. }
  288. };
  289. }
  290. #[test]
  291. fn less_than_64() {
  292. test_circuit!(3, 64, 22);
  293. let k = 5;
  294. let valid_pairs = [
  295. (pallas::Base::from(13), pallas::Base::from(15)),
  296. (pallas::Base::ZERO, pallas::Base::from(u64::MAX)),
  297. (pallas::Base::ONE, pallas::Base::from(rand::random::<u64>())),
  298. ];
  299. let invalid_pairs = [
  300. (pallas::Base::from(14), pallas::Base::from(11)),
  301. (pallas::Base::from(u64::MAX), pallas::Base::ZERO),
  302. (pallas::Base::ZERO, pallas::Base::ZERO),
  303. (pallas::Base::ONE, pallas::Base::ONE),
  304. (pallas::Base::ONE, pallas::Base::ZERO),
  305. (pallas::Base::from(u64::MAX), pallas::Base::from(u64::MAX)),
  306. (pallas::Base::from(u64::MAX), pallas::Base::from(u64::MAX) + pallas::Base::ONE),
  307. ];
  308. use plotters::prelude::*;
  309. let circuit = LessThanCircuit {
  310. a: Value::known(pallas::Base::zero()),
  311. b: Value::known(pallas::Base::one()),
  312. };
  313. let root = BitMapBackend::new("target/lessthan_circuit_layout.png", (3840, 2160))
  314. .into_drawing_area();
  315. CircuitLayout::default().render(k, &circuit, &root).unwrap();
  316. for (a, b) in valid_pairs {
  317. println!("64 bit (valid) {:?} < {:?} check", a, b);
  318. let circuit = LessThanCircuit { a: Value::known(a), b: Value::known(b) };
  319. let prover = MockProver::run(k, &circuit, vec![]).unwrap();
  320. prover.assert_satisfied();
  321. }
  322. for (a, b) in invalid_pairs {
  323. println!("64 bit (invalid) {:?} < {:?} check", a, b);
  324. let circuit = LessThanCircuit { a: Value::known(a), b: Value::known(b) };
  325. let prover = MockProver::run(k, &circuit, vec![]).unwrap();
  326. assert!(prover.verify().is_err())
  327. }
  328. }
  329. #[test]
  330. fn less_than_253() {
  331. test_circuit!(3, 253, 85);
  332. let k = 7;
  333. const P_MINUS_1: pallas::Base = pallas::Base::from_raw([
  334. 0x992d30ed00000000,
  335. 0x224698fc094cf91b,
  336. 0x0000000000000000,
  337. 0x4000000000000000,
  338. ]);
  339. // 2^253 - 1. This is the maximum we can check.
  340. const MAX_253: pallas::Base = pallas::Base::from_raw([
  341. 0xFFFFFFFFFFFFFFFF,
  342. 0xFFFFFFFFFFFFFFFF,
  343. 0xFFFFFFFFFFFFFFFF,
  344. 0x1FFFFFFFFFFFFFFF,
  345. ]);
  346. let valid_pairs = [
  347. (pallas::Base::ZERO, pallas::Base::ONE),
  348. (pallas::Base::from(u64::MAX), pallas::Base::from(u64::MAX) + pallas::Base::ONE),
  349. (
  350. pallas::Base::from_u128(u128::MAX),
  351. pallas::Base::from_u128(u128::MAX) + pallas::Base::ONE,
  352. ),
  353. (MAX_253 - pallas::Base::from(2), MAX_253 - pallas::Base::ONE),
  354. (MAX_253 - pallas::Base::ONE, MAX_253),
  355. (MAX_253, MAX_253 + pallas::Base::ONE), // <-- Is this legit??
  356. ];
  357. let invalid_pairs = [
  358. (pallas::Base::ZERO, pallas::Base::ZERO),
  359. (pallas::Base::ONE, pallas::Base::ZERO),
  360. (P_MINUS_1 - pallas::Base::ONE, P_MINUS_1),
  361. (P_MINUS_1, P_MINUS_1),
  362. (P_MINUS_1, pallas::Base::ZERO),
  363. (MAX_253, MAX_253),
  364. (MAX_253, pallas::Base::ZERO),
  365. (MAX_253, pallas::Base::ONE),
  366. (MAX_253 + pallas::Base::ONE, pallas::Base::ZERO),
  367. (MAX_253 + pallas::Base::ONE, pallas::Base::ONE),
  368. (MAX_253 + pallas::Base::ONE, MAX_253 + pallas::Base::ONE),
  369. (MAX_253 + pallas::Base::ONE, MAX_253 + pallas::Base::from(2)),
  370. ];
  371. for (a, b) in valid_pairs {
  372. println!("253 bit (valid) {:?} < {:?} check", a, b);
  373. let circuit = LessThanCircuit { a: Value::known(a), b: Value::known(b) };
  374. let prover = MockProver::run(k, &circuit, vec![]).unwrap();
  375. prover.assert_satisfied();
  376. }
  377. for (a, b) in invalid_pairs {
  378. println!("253 bit (invalid) {:?} < {:?} check", a, b);
  379. let circuit = LessThanCircuit { a: Value::known(a), b: Value::known(b) };
  380. let prover = MockProver::run(k, &circuit, vec![]).unwrap();
  381. assert!(prover.verify().is_err())
  382. }
  383. }
  384. }