diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..36195ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Hideaki Takahashi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index a21b7db..95e4d48 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# MyZKP: Building Zero Knowledge Proof from Scratch - ``` ███╗ ███╗ ██╗ ██╗ ███████╗ ██╗ ██╗ ██████╗ ████╗ ████║ ╚██╗ ██╔╝ ╚══███╔╝ ██║ ██╔╝ ██╔══██╗ @@ -7,4 +5,9 @@ ██║╚██╔╝██║ ╚██╔╝ ███╔╝ ██╔═██╗ ██╔═══╝ ██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██╗ ██║ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ -``` \ No newline at end of file +``` + +# Building Zero Knowledge Proof from Scratch in Rust + +> [!WARNING] +> Warn: This repo is still working in progress. It is likely to contain bugs/wrong-information. \ No newline at end of file diff --git a/book/book.toml b/book/book.toml index bc276bd..943efad 100644 --- a/book/book.toml +++ b/book/book.toml @@ -3,8 +3,9 @@ authors = ["Koukyosyumei"] language = "en" multilingual = false src = "src" -title = "MyZKP: Building Zero Knowledge Proof from Scratch" +title = "Book of MyZKP" [output.html] mathjax-support = true additional-css = ["theme/css/custom.css"] +it-repository-url = "https://github.com/Koukyosyumei/MyZKP/tree/main" diff --git a/book/src/README.md b/book/src/README.md index e69de29..52b4454 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -0,0 +1,10 @@ +# MyZKP: Building Zero Knowledge Proof from Scratch in Rust + +``` +███╗ ███╗ ██╗ ██╗ ███████╗ ██╗ ██╗ ██████╗ +████╗ ████║ ╚██╗ ██╔╝ ╚══███╔╝ ██║ ██╔╝ ██╔══██╗ +██╔████╔██║ ╚████╔╝ ███╔╝ █████╔╝ ██████╔╝ +██║╚██╔╝██║ ╚██╔╝ ███╔╝ ██╔═██╗ ██╔═══╝ +██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██╗ ██║ +╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ +``` \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index af51594..3aafa95 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -5,9 +5,10 @@ - [Computation Rule and Properties](./number_theory/subsec1.md) - [Semigroup, Group, Ring](./number_theory/subsec2.md) - [Polynomials](./number_theory/subsec3.md) - - [Galoid Field](./number_theory/subsec4.md) + - [Galois Field](./number_theory/subsec4.md) - [Elliptic Curve](./number_theory/subsec5.md) - - [Useful Assumptions](./number_theory/subsec6.md) + - [Pairing](./number_theory/subsec6.md) + - [Useful Assumptions](./number_theory/subsec7.md) - [Basics of zk-SNARKs](./zksnark/README.md) - [Arithmetization](./zksnark/subsec2.md) - [Proving Single Polynomial](./zksnark/subsec3.md) diff --git a/book/src/number_theory/subsec1.md b/book/src/number_theory/subsec1.md index 10a4251..59a9b2c 100644 --- a/book/src/number_theory/subsec1.md +++ b/book/src/number_theory/subsec1.md @@ -9,7 +9,7 @@ --- -**Example**: Addition (+) on the set of integers is a binary operation. For example, \\(5 + 3 = 8\\), and both \\(5, 3, 8\\) are integers, staying within the set of integers. +**Example:** Addition (+) on the set of integers is a binary operation. For example, \\(5 + 3 = 8\\), and both \\(5, 3, 8\\) are integers, staying within the set of integers. ### Definition: Associative Property @@ -19,7 +19,7 @@ --- -**Example**: Multiplication of real numbers is associative: \\((2 \times 3) \times 4 = 2 \times (3 \times 4) = 24\\). In a modular context, we also have addition modulo \\(n\\) being associative. For example, for \\(n = 5\\), \\((2 + 3) \bmod 5 + 4 \bmod 5 = 2 + (3 \bmod 5 + 4) \bmod 5 = 4\\). +**Example:** Multiplication of real numbers is associative: \\((2 \times 3) \times 4 = 2 \times (3 \times 4) = 24\\). In a modular context, we also have addition modulo \\(n\\) being associative. For example, for \\(n = 5\\), \\((2 + 3) \bmod 5 + 4 \bmod 5 = 2 + (3 \bmod 5 + 4) \bmod 5 = 4\\). ### Definition: Commutative Property @@ -29,4 +29,4 @@ --- -**Example**: Addition modulo \\(n\\) is also commutative. For \\(n = 7\\), \\(5 + 3 \bmod 7 = 3 + 5 \bmod 7 = 1\\). +**Example:** Addition modulo \\(n\\) is also commutative. For \\(n = 7\\), \\(5 + 3 \bmod 7 = 3 + 5 \bmod 7 = 1\\). diff --git a/book/src/number_theory/subsec2.md b/book/src/number_theory/subsec2.md index d161f4c..3dd8f15 100644 --- a/book/src/number_theory/subsec2.md +++ b/book/src/number_theory/subsec2.md @@ -8,7 +8,7 @@ --- -**Example**: The set of positive integers under multiplication modulo \\(n\\) forms a semigroup. For instance, with \\(n = 6\\), the elements \\(\\{1, 2, 3, 4, 5\\}\\) under multiplication modulo 6 form a semigroup, since multiplication modulo 6 is associative. +**Example:** The set of positive integers under multiplication modulo \\(n\\) forms a semigroup. For instance, with \\(n = 6\\), the elements \\(\\{1, 2, 3, 4, 5\\}\\) under multiplication modulo 6 form a semigroup, since multiplication modulo 6 is associative. ### Definition: Abelian Semigroup @@ -19,7 +19,7 @@ --- -**Example**: The set of natural numbers under addition modulo \\(n\\) forms an abelian semigroup. For \\(n = 7\\), addition modulo 7 is both associative and commutative, so it is an abelian semigroup. +**Example:** The set of natural numbers under addition modulo \\(n\\) forms an abelian semigroup. For \\(n = 7\\), addition modulo 7 is both associative and commutative, so it is an abelian semigroup. ### Definition: Identity Element @@ -30,7 +30,7 @@ --- -**Example**: 0 is the identity element for addition modulo \\(n\\). For example, \\(0 + a \bmod 5 = a + 0 \bmod 5 = a\\). Similarly, 1 is the identity element for multiplication modulo \\(n\\). For example, \\(1 \times a \bmod 7 = a \times 1 \bmod 7 = a\\). +**Example:** 0 is the identity element for addition modulo \\(n\\). For example, \\(0 + a \bmod 5 = a + 0 \bmod 5 = a\\). Similarly, 1 is the identity element for multiplication modulo \\(n\\). For example, \\(1 \times a \bmod 7 = a \times 1 \bmod 7 = a\\). ### Definition: Monoid @@ -40,7 +40,7 @@ --- -**Example**: The set of non-negative integers under addition modulo \\(n\\) forms a monoid. For \\(n = 5\\), the set \\(\\{0, 1, 2, 3, 4\\}\\) under addition modulo 5 forms a monoid with 0 as the identity element. +**Example:** The set of non-negative integers under addition modulo \\(n\\) forms a monoid. For \\(n = 5\\), the set \\(\\{0, 1, 2, 3, 4\\}\\) under addition modulo 5 forms a monoid with 0 as the identity element. ### Definition: Inverse @@ -50,7 +50,7 @@ --- -**Example**: In modulo \\(n\\) arithmetic (addition), the inverse of an element exists if it can cancel itself out to yield the identity element. In the set of integers modulo 7, the inverse of 3 is 5, because \\(3 \times 5 \bmod 7 = 1\\), where 1 is the identity element for multiplication. +**Example:** In modulo \\(n\\) arithmetic (addition), the inverse of an element exists if it can cancel itself out to yield the identity element. In the set of integers modulo 7, the inverse of 3 is 5, because \\(3 \times 5 \bmod 7 = 1\\), where 1 is the identity element for multiplication. ### Definition: Group @@ -60,7 +60,7 @@ --- -**Example**: The set of integers modulo a prime \\(p\\) under multiplication forms a group (Can you prove it?). For instance, in \\(\mathbb{Z}/5\mathbb{Z}\\), every non-zero element \\(\\{1 + 5\mathbb{Z}, 2 + 5\mathbb{Z}, 3 + 5\mathbb{Z}, 4 + 5\mathbb{Z}\\}\\) has an inverse, making it a group. +**Example:** The set of integers modulo a prime \\(p\\) under multiplication forms a group (Can you prove it?). For instance, in \\(\mathbb{Z}/5\mathbb{Z}\\), every non-zero element \\(\\{1 + 5\mathbb{Z}, 2 + 5\mathbb{Z}, 3 + 5\mathbb{Z}, 4 + 5\mathbb{Z}\\}\\) has an inverse, making it a group. ### Definition: Order of a Group @@ -70,7 +70,7 @@ --- -**Example**: The group of integers modulo 4 under addition has order 4, because the set of elements is \\(\\{0, 1, 2, 3\\}\\). +**Example:** The group of integers modulo 4 under addition has order 4, because the set of elements is \\(\\{0, 1, 2, 3\\}\\). ### Definition: Ring @@ -81,9 +81,13 @@ --- -**Example**: The set of integers with usual addition and multiplication modulo \\(n\\) forms a ring. For example, in \\(\mathbb{Z}/6\mathbb{Z}\\), addition and multiplication modulo 6 form a ring. +**Example:** The set of integers with usual addition and multiplication modulo \\(n\\) forms a ring. For example, in \\(\mathbb{Z}/6\mathbb{Z}\\), addition and multiplication modulo 6 form a ring. + +**Implementation:** ```rust +use num_bigint::BigInt; +use num_traits::{One, Zero}; use std::fmt; use std::ops::{Add, Mul, Neg, Sub}; @@ -93,14 +97,25 @@ pub trait Ring: + PartialEq + fmt::Display + Add + + for<'a> Add<&'a Self, Output = Self> + Sub + + for<'a> Sub<&'a Self, Output = Self> + Mul - + Mul + + for<'a> Mul<&'a Self, Output = Self> + Neg + + One + + Zero { // A ring is an algebraic structure with addition and multiplication - fn zero() -> Self; - fn one() -> Self; + fn add_ref(&self, rhs: &Self) -> Self; + fn sub_ref(&self, rhs: &Self) -> Self; + fn mul_ref(&self, rhs: &Self) -> Self; + + // Utility functions + fn pow>(&self, n: M) -> Self; + fn get_value(&self) -> BigInt; + fn from_value>(value: M) -> Self; + fn random_element(exclude_elements: &[Self]) -> Self; } ``` @@ -124,11 +139,13 @@ pub trait Ring: **Example** The set of rational numbers under usual addition and multiplication forms a field. -```rust -use crate::modules::ring::Ring; +**Implementation:** -pub trait Field: Ring + Div { +```rust +pub trait Field: Ring + Div + PartialEq + Eq + Hash { + /// Computes the multiplicative inverse of the element. fn inverse(&self) -> Self; + fn div_ref(&self, other: &Self) -> Self; } ``` @@ -153,7 +170,7 @@ pub trait Field: Ring + Div { **Example:** In \\( \mathbb{Z}/5\mathbb{Z} \\), \\( 3 + 5\mathbb{Z} \\) is invertible because \\( \gcd(3, 5) = 1 \\) (since \\( 3\cdot2 \equiv 1 \pmod{5} \\)). However, in \\( \mathbb{Z}/6\mathbb{Z} \\), \\( 3 + 6\mathbb{Z} \\) is not invertible because \\( \gcd(3, 6) = 3 \neq 1 \\). -### Lemma 1.2.1 +### Lemma 2.2.1 --- @@ -163,7 +180,7 @@ pub trait Field: Ring + Div { **Proof:** Suppose, for contradiction, that there exist \\( x, y \in \\{1, 2, \dots, b-1\\} \\) with \\( x \neq y \\) such that \\( xa \equiv ya \pmod{b} \\). Then \\( (x-y)a \equiv 0 \pmod{b} \\), implying \\( b \mid (x-y)a \\). Since \\( a \\) and \\( b \\) are coprime, we must have \\( b \mid (x-y) \\). However, \\( |x-y| < b \\), so this is only possible if \\( x = y \\), contradicting our assumption. Therefore, all residues must be distinct. -### Theorem 1.2.2 +### Theorem 2.2.2 --- @@ -193,14 +210,14 @@ This statement is equivalent to proving that \\( ax + by = c \\) has an integer - **(\\( \Rightarrow \\))** If \\( ax + by = c \\) for some integers \\( x \\) and \\( y \\), then \\( \gcd(a,b) \mid a \\) and \\( \gcd(a,b) \mid b \\), so \\( \gcd(a,b) \mid (ax + by) = c \\). - **(\\( \Leftarrow \\))** Let \\( c = k\gcd(a,b) \\) for some integer \\( k \\). We can write \\( a = p\gcd(a,b) \\) and \\( b = q\gcd(a,b) \\), where \\( p \\) and \\( q \\) are coprime. By the previous theorem, there exist integers \\( m \\) and \\( n \\) such that \\( pm + qn = 1 \\). Multiplying both sides by \\( k\gcd(a,b) \\), we get: - \[ + \\[ akm + bkn = c - \] + \\] Thus, \\( x = km \\) and \\( y = kn \\) are integer solutions to \\( ax + by = c \\). This theorem implies that for any integers \\( a \\), \\( b \\), and \\( n \\), the equation \\( ax + by = n \\) has an integer solution if and only if \\( \gcd(a,b) \mid n \\). -### Theorem 1.2.3 +### Theorem 2.2.3 --- @@ -215,8 +232,12 @@ This theorem implies that for any integers \\( a \\), \\( b \\), and \\( n \\), ### Definition: Residue Class Ring +--- + *\\( (\mathbb{Z} / m \mathbb{Z}, +, \cdot) \\) is a commutative ring where \\( 1 + m \mathbb{Z} \\) is the multiplicative identity element. This ring is called the residue class ring modulo \\( m \\).* +--- + **Example:** \\( \mathbb{Z}/4\mathbb{Z} = \\{0 + 4\mathbb{Z}, 1 + 4\mathbb{Z}, 2 + 4\mathbb{Z}, 3 + 4\mathbb{Z}\\} \\). ### Definition: Primitive Residue Class @@ -229,7 +250,7 @@ This theorem implies that for any integers \\( a \\), \\( b \\), and \\( n \\), **Example:** In \\( \mathbb{Z}/6\mathbb{Z} \\), the primitive residue classes are \\( 1 + 6\mathbb{Z} \\) and \\( 5 + 6\mathbb{Z} \\). -### Theorem 1.2.4 +### Theorem 2.2.4 --- @@ -261,7 +282,7 @@ Contrast this with \\( m = 9 \\). The primitive residue class group is \\((\math --- -**Example**: For \\(m = 12\\), \\(\phi(12) = 4\\) because there are 4 integers less than 12 that are coprime to 12: \\(\{1, 5, 7, 11\}\\). +**Example:** For \\(m = 12\\), \\(\phi(12) = 4\\) because there are 4 integers less than 12 that are coprime to 12: \\(\{1, 5, 7, 11\}\\). For \\(m = 10\\), \\(\phi(10) = 4\\), as there are also 4 integers less than 10 that are coprime to 10: \\(\{1, 3, 7, 9\}\\). @@ -273,7 +294,7 @@ For \\(m = 10\\), \\(\phi(10) = 4\\), as there are also 4 integers less than 10 --- -**Example**: In \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), the element 3 has order 6 because \\(3^6 \bmod 7 = 1\\). In other words, \\(3 \times 3 \times 3 \times 3 \times 3 \times 3 \bmod 7 = 1\\), and 6 is the smallest such exponent. +**Example:** In \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), the element 3 has order 6 because \\(3^6 \bmod 7 = 1\\). In other words, \\(3 \times 3 \times 3 \times 3 \times 3 \times 3 \bmod 7 = 1\\), and 6 is the smallest such exponent. ### Definition: Subgroup @@ -283,7 +304,7 @@ For \\(m = 10\\), \\(\phi(10) = 4\\), as there are also 4 integers less than 10 --- -**Example**: Consider \\((\mathbb{Z}/8\mathbb{Z})^{\times} = \\{1 + 8\mathbb{Z}, 3+ 8\mathbb{Z}, 5+ 8\mathbb{Z}, 7+ 8\mathbb{Z}\\}\\) under multiplication modulo 8. The subset \\(\{1+ 8\mathbb{Z}, 7+ 8\mathbb{Z}\}\\) forms a subgroup because it satisfies the group properties: closed under multiplication, contains the identity element (1), and every element has an inverse (\\(7 \times 7 \equiv 1 \bmod 8\\)). +**Example:** Consider \\((\mathbb{Z}/8\mathbb{Z})^{\times} = \\{1 + 8\mathbb{Z}, 3+ 8\mathbb{Z}, 5+ 8\mathbb{Z}, 7+ 8\mathbb{Z}\\}\\) under multiplication modulo 8. The subset \\(\{1+ 8\mathbb{Z}, 7+ 8\mathbb{Z}\}\\) forms a subgroup because it satisfies the group properties: closed under multiplication, contains the identity element (1), and every element has an inverse (\\(7 \times 7 \equiv 1 \bmod 8\\)). ### Definition: Subgroup Generated by \\(g\\) @@ -293,7 +314,7 @@ For \\(m = 10\\), \\(\phi(10) = 4\\), as there are also 4 integers less than 10 --- -**Example**: Consider the group \\((\mathbb{Z}/7\mathbb{Z})^{\times} = \\{1+ 7\mathbb{Z}, 2+ 7\mathbb{Z}, 3+ 7\mathbb{Z}, 4+ 7\mathbb{Z}, 5+ 7\mathbb{Z}, 6+ 7\mathbb{Z}\\}\\) under multiplication modulo 7. If we take \\(g = 3\\), then \\(\langle 3 +7\mathbb{Z} \rangle =\\) \\(\\{3^1+7\mathbb{Z}, 3^2+7\mathbb{Z}, 3^3+7\mathbb{Z}, 3^4+7\mathbb{Z}, 3^5+7\mathbb{Z}, 3^6+7\mathbb{Z}\\} \bmod 7 =\\) \\(\\{3+7\mathbb{Z}, 2+7\mathbb{Z}, 6+7\mathbb{Z}, 4+7\mathbb{Z}, 5+7\mathbb{Z}, 1+7\mathbb{Z}\\}\\), which forms a subgroup generated by 3. This subgroup contains all elements of \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), making 3 a generator of the entire group. +**Example:** Consider the group \\((\mathbb{Z}/7\mathbb{Z})^{\times} = \\{1+ 7\mathbb{Z}, 2+ 7\mathbb{Z}, 3+ 7\mathbb{Z}, 4+ 7\mathbb{Z}, 5+ 7\mathbb{Z}, 6+ 7\mathbb{Z}\\}\\) under multiplication modulo 7. If we take \\(g = 3\\), then \\(\langle 3 +7\mathbb{Z} \rangle =\\) \\(\\{3^1+7\mathbb{Z}, 3^2+7\mathbb{Z}, 3^3+7\mathbb{Z}, 3^4+7\mathbb{Z}, 3^5+7\mathbb{Z}, 3^6+7\mathbb{Z}\\} \bmod 7 =\\) \\(\\{3+7\mathbb{Z}, 2+7\mathbb{Z}, 6+7\mathbb{Z}, 4+7\mathbb{Z}, 5+7\mathbb{Z}, 1+7\mathbb{Z}\\}\\), which forms a subgroup generated by 3. This subgroup contains all elements of \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), making 3 a generator of the entire group. If \\(g\\) has a finite order \\(e\\), we have that \\(\langle g \rangle = \\{g^{k}: 0 \leq k \leq e\\}\\), meaning \\(e\\) is the order of \\(\langle g \rangle\\). @@ -305,9 +326,9 @@ If \\(g\\) has a finite order \\(e\\), we have that \\(\langle g \rangle = \\{g^ --- -**Example**: The group \\((\mathbb{Z}/6\mathbb{Z})^{\times} = \\{1+6\mathbb{Z}, 5+6\mathbb{Z}\\}\\) under multiplication modulo 6 is a cyclic group. In this case, both 1 and 5 are generators of the group because \\(\langle 5 +6\mathbb{Z} \rangle = \\{(5^1 \bmod 6)+6\mathbb{Z} = 5 +6\mathbb{Z}, (5^2 \bmod 6)+6\mathbb{Z} = 1+6\mathbb{Z}\\}\\). Since 5 generates all the elements of the group, \\(G\\) is cyclic. +**Example:** The group \\((\mathbb{Z}/6\mathbb{Z})^{\times} = \\{1+6\mathbb{Z}, 5+6\mathbb{Z}\\}\\) under multiplication modulo 6 is a cyclic group. In this case, both 1 and 5 are generators of the group because \\(\langle 5 +6\mathbb{Z} \rangle = \\{(5^1 \bmod 6)+6\mathbb{Z} = 5 +6\mathbb{Z}, (5^2 \bmod 6)+6\mathbb{Z} = 1+6\mathbb{Z}\\}\\). Since 5 generates all the elements of the group, \\(G\\) is cyclic. -### Theorem 1.2.5 +### Theorem 2.2.5 --- @@ -317,9 +338,9 @@ If \\(g\\) has a finite order \\(e\\), we have that \\(\langle g \rangle = \\{g^ **Proof**: TBD -**Example**: Consider the group \\((\mathbb{Z}/8\mathbb{Z})^{\times} = \\{1+8\mathbb{Z}, 3+8\mathbb{Z}, 5+8\mathbb{Z}, 7+8\mathbb{Z}\\}\\). This group is cyclic, and \\(\phi(8) = 4\\). The generators of this group are \\(\\{1+8\mathbb{Z}, 3+8\mathbb{Z}, 5+8\mathbb{Z}, 7+8\mathbb{Z}\\}\\), each of which generates the entire group when raised to successive powers modulo 8. Each generator has the same order, which is \\(|G| = 4\\). +**Example:** Consider the group \\((\mathbb{Z}/8\mathbb{Z})^{\times} = \\{1+8\mathbb{Z}, 3+8\mathbb{Z}, 5+8\mathbb{Z}, 7+8\mathbb{Z}\\}\\). This group is cyclic, and \\(\phi(8) = 4\\). The generators of this group are \\(\\{1+8\mathbb{Z}, 3+8\mathbb{Z}, 5+8\mathbb{Z}, 7+8\mathbb{Z}\\}\\), each of which generates the entire group when raised to successive powers modulo 8. Each generator has the same order, which is \\(|G| = 4\\). -### Theorem 1.2.6 +### Theorem 2.2.6 --- @@ -329,7 +350,7 @@ If \\(g\\) has a finite order \\(e\\), we have that \\(\langle g \rangle = \\{g^ **Proof**: TBD -**Example**: Consider the cyclic group \\((\mathbb{Z}/6\mathbb{Z})^{\times} = \\{1+6\mathbb{Z}, 5+6\mathbb{Z}\\}\\) under multiplication modulo 6. If we take the subgroup \\(\langle 5+6\mathbb{Z} \rangle = \\{1+6\mathbb{Z}, 5+6\mathbb{Z}\\}\\), this is a subgroup of order 2, and 2 divides the order of the original group, which is 6. This theorem generalizes this property: for any subgroup of a cyclic group, its order divides the order of the group. +**Example:** Consider the cyclic group \\((\mathbb{Z}/6\mathbb{Z})^{\times} = \\{1+6\mathbb{Z}, 5+6\mathbb{Z}\\}\\) under multiplication modulo 6. If we take the subgroup \\(\langle 5+6\mathbb{Z} \rangle = \\{1+6\mathbb{Z}, 5+6\mathbb{Z}\\}\\), this is a subgroup of order 2, and 2 divides the order of the original group, which is 6. This theorem generalizes this property: for any subgroup of a cyclic group, its order divides the order of the group. ### Theorem: Fermat's Little Theorem @@ -341,11 +362,11 @@ If \\(g\\) has a finite order \\(e\\), we have that \\(\langle g \rangle = \\{g^ **Proof**: TBD -**Example**: Take \\(a = 2\\) and \\(m = 5\\). Since \\(\gcd(2, 5) = 1\\), Fermat's Little Theorem tells us that \\(2^{\phi(5)} = 2^4 \equiv 1 \bmod 5\\). Indeed, \\(2^4 = 16\\) and \\(16 \bmod 5 = 1\\). +**Example:** Take \\(a = 2\\) and \\(m = 5\\). Since \\(\gcd(2, 5) = 1\\), Fermat's Little Theorem tells us that \\(2^{\phi(5)} = 2^4 \equiv 1 \bmod 5\\). Indeed, \\(2^4 = 16\\) and \\(16 \bmod 5 = 1\\). This theorem suggests that \\(a^{\phi(m) - 1} + m \mathbb{Z}\\) is the inverse residue class of \\(a + m \mathbb{Z}\\). -### Theorem 1.2.7 +### Theorem 2.2.7 --- @@ -355,7 +376,7 @@ This theorem suggests that \\(a^{\phi(m) - 1} + m \mathbb{Z}\\) is the inverse r **Proof**: TBD -**Example**: In the group \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), consider the element \\(3 + 7\mathbb{Z}\\). The order of \\(3 + 7\mathbb{Z}\\) is 6, as \\(3^6 \equiv 1 \bmod 7\\). The order of the group itself is also 6, and indeed, the order of the element divides the order of the group. +**Example:** In the group \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), consider the element \\(3 + 7\mathbb{Z}\\). The order of \\(3 + 7\mathbb{Z}\\) is 6, as \\(3^6 \equiv 1 \bmod 7\\). The order of the group itself is also 6, and indeed, the order of the element divides the order of the group. ### Theorem: Generalization of Fermat's Little Theorem @@ -367,7 +388,7 @@ This theorem suggests that \\(a^{\phi(m) - 1} + m \mathbb{Z}\\) is the inverse r **Proof**: TBD -**Example**: In the group \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), for any element \\(g\\), such as \\(g = 3 + 7\mathbb{Z}\\), we have \\(3^6 \equiv 1 \bmod 7\\). This holds for any \\(g \in (\mathbb{Z}/7\mathbb{Z})^{\times}\\) because the order of the group is 6. Thus, \\(g^{|G|} = 1\\) is satisfied. +**Example:** In the group \\((\mathbb{Z}/7\mathbb{Z})^{\times}\\), for any element \\(g\\), such as \\(g = 3 + 7\mathbb{Z}\\), we have \\(3^6 \equiv 1 \bmod 7\\). This holds for any \\(g \in (\mathbb{Z}/7\mathbb{Z})^{\times}\\) because the order of the group is 6. Thus, \\(g^{|G|} = 1\\) is satisfied. diff --git a/book/src/number_theory/subsec3.md b/book/src/number_theory/subsec3.md index 39839a5..695eeeb 100644 --- a/book/src/number_theory/subsec3.md +++ b/book/src/number_theory/subsec3.md @@ -1,26 +1,34 @@ # Polynomials -Let \\(K\\) be a field. - ### Definition: Polynomial +--- + *A univariate polynomial over a commutative ring \\(R\\) with unity \\(1\\) is an expression of the form \\(f(x) = a_n x^{n} + a_{n-1} x^{n-1} + \cdots + a_1 x + a_0\\), where \\(x\\) is a variable and coefficients \\(a_0, \ldots, a_n\\) belong to \\(R\\). The set of all polynomials over \\(R\\) in the variable \\(x\\) is denoted as \\(R[x]\\).* +--- + **Example:** In \\(\mathbb{Z}[x]\\), we have polynomials such as \\(2x^3 + x + 1\\), \\(x\\), and \\(1\\). In \\(\mathbb{R}[x]\\), we have polynomials like \\(\pi x^2 - \sqrt{2}x + e\\). +**Implementation:** + ```rust -/// Polynomial struct representing a polynomial over Field. +/// A struct representing a polynomial over a finite field. #[derive(Debug, Clone, PartialEq)] pub struct Polynomial { - pub poly: Vec, - pub var: String, + /// Coefficients of the polynomial in increasing order of degree. + pub coef: Vec, } ``` ### Definition: Degree +--- + *The degree of a non-zero polynomial \\(f(x) = a_n x^{n} + a_{n-1} x^{n-1} + \cdots + a_1 x + a_0\\), denoted as \\(\deg f\\), is the largest integer \\(n\\) such that \\(a_n \neq 0\\). The zero polynomial is defined to have degree \\(-1\\).* +--- + **Example** - \\(\deg(2x^3 + x + 1) = 3\\) @@ -28,6 +36,8 @@ pub struct Polynomial { - \\(\deg(1) = 0\\) - \\(\deg(0) = -1\\) +**Implementation:** + ```rust impl Polynomial { /// Removes trailing zeroes from a polynomial's coefficients. @@ -51,74 +61,108 @@ impl Polynomial { } ``` -### Sum of Polynomials +### Definition: Sum of Polynomials + +--- *For polynomials \\(f(x) = \sum_{i=0}^n a_i x^i\\) and \\(g(x) = \sum_{i=0}^m b_i x^i\\), their sum is defined as: \\((f + g)(x) = \sum_{i=0}^{\max(n,m)} (a_i + b_i) x^i\\), where we set \\(a_i = 0\\) for \\(i > n\\) and \\(b_i = 0\\) for \\(i > m\\).* +--- + **Example:** Let \\(f(x) = 2x^2 + 3x + 1\\) and \\(g(x) = x^3 - x + 4\\). Then, \\((f + g)(x) = x^3 + 2x^2 + 2x + 5\\) -```rust -impl Add for Polynomial { - type Output = Self; +**Implementation:** - fn add(self, other: Self) -> Polynomial { - let max_len = std::cmp::max(self.poly.len(), other.poly.len()); +```rust +impl Polynomial { + fn add_ref<'b>(&self, other: &'b Polynomial) -> Polynomial { + let max_len = std::cmp::max(self.coef.len(), other.coef.len()); let mut result = Vec::with_capacity(max_len); - let zero = F::zero(None); + let zero = F::zero(); for i in 0..max_len { - let a = self.poly.get(i).unwrap_or(&zero); - let b = other.poly.get(i).unwrap_or(&zero); - result.push(a.clone() + b.clone()); + let a = self.coef.get(i).unwrap_or(&zero); + let b = other.coef.get(i).unwrap_or(&zero); + result.push(a.add_ref(b)); } Polynomial { - poly: Self::trim_trailing_zeros(result), - var: self.var.clone(), + coef: Self::trim_trailing_zeros(result), } } } + +impl Add for Polynomial { + type Output = Self; + + fn add(self, other: Self) -> Polynomial { + self.add_ref(&other) + } +} ``` -### Product of polynomials +### Definition: Product of polynomials + +--- *For polynomials \\(f(x) = \sum_{i=0}^n a_i x^i\\) and \\(g(x) = \sum_{j=0}^m b_j x^j\\), their product is defined as: \\((fg)(x) = \sum_{k=0}^{n+m} c_k x^k\\), where \\(c_k = \sum_{i+j=k} a_i b_j\\)* +--- + **Example:** Let \\(f(x) = x + 1\\) and \\(g(x) = x^2 - 1\\). Then, \\((fg)(x) = x^3 + x^2 - x - 1\\) -```rust -impl Mul for Polynomial { - type Output = Self; +**Implementation:** - /// Multiplication of two polynomials. - fn mul(self, other: Polynomial) -> Polynomial { - let mut result = vec![F::zero(None); self.degree() as usize + other.degree() as usize + 1]; +```rust +impl Polynomial { + fn mul_ref<'b>(&self, other: &'b Polynomial) -> Polynomial { + if self.is_zero() || other.is_zero() { + return Polynomial::::zero(); + } + let mut result = vec![F::zero(); (self.degree() + other.degree() + 1) as usize]; - for (i, a) in self.poly.iter().enumerate() { - for (j, b) in other.poly.iter().enumerate() { - result[i + j] = result[i + j].clone() + (a.clone() * b.clone()); + for (i, a) in self.coef.iter().enumerate() { + for (j, b) in other.coef.iter().enumerate() { + result[i + j] = result[i + j].add_ref(&a.mul_ref(b)); } } Polynomial { - poly: Self::trim_trailing_zeros(result), - var: self.var.clone(), + coef: Polynomial::::trim_trailing_zeros(result), } } } + +impl Mul> for Polynomial { + type Output = Self; + + fn mul(self, other: Polynomial) -> Polynomial { + self.mul_ref(&other) + } +} ``` -### Lemma +### Lemma 2.3.1 + +Let \\(K\\) be a field. + +--- *Let \\(f, g \in K[x]\\) be non-zero polynomials. Then, \\(\deg(fg) = \deg f + \deg g\\).* +--- **Example:** Let \\(f(x) = x^2 + 1\\) and \\(g(x) = x^3 - x\\) in \\(\mathbb{R}[x]\\). Then, \\(\deg(fg) = \deg(x^5 - x^3 + x^2 + 1) = 5 = 2 + 3 = \deg f + \deg g\\) +### Theorem 2.3.2 + We can also define division in the polynomial ring \\(K[x]\\). -### Theorem +--- + *Let \\(f, g \in K[x]\\), with \\(g \neq 0\\). There exist unique polynomials \\(q, r \in K[x]\\) that satisfy \\(f = qg + r\\) and either \\(\deg r < \deg g\\) or \\(r = 0\\).* +--- + **Proof** TBD @@ -127,60 +171,80 @@ We can also define division in the polynomial ring \\(K[x]\\). **Example:** In \\(\mathbb{R}[x]\\), let \\(f(x) = x^3 + 2x^2 - x + 3\\) and \\(g(x) = x^2 + 1\\). Then \\(f = qg + r\\) where \\(q(x) = x + 2\\) and \\(r(x) = -3x + 1\\). +**Implementation:** + ```rust -impl Div for Polynomial { - type Output = Self; +impl Polynomial { + fn div_rem_ref<'b>(&self, other: &'b Polynomial) -> (Polynomial, Polynomial) { + if self.degree() < other.degree() { + return (Polynomial::zero(), self.clone()); + } - /// Division of two polynomials, returns quotient. - fn div(self, other: Polynomial) -> Polynomial { - let mut remainder_coeffs = Self::trim_trailing_zeros(self.poly.clone()); - let divisor_coeffs = Self::trim_trailing_zeros(other.poly.clone()); + let mut remainder_coeffs = Self::trim_trailing_zeros(self.coef.clone()); + let divisor_coeffs = Self::trim_trailing_zeros(other.coef.clone()); let divisor_lead_inv = divisor_coeffs.last().unwrap().inverse(); - let mut quotient = - vec![F::zero(None); self.degree() as usize - other.degree() as usize + 1]; + let mut quotient = vec![F::zero(); self.degree() as usize - other.degree() as usize + 1]; - let mut i = 0_i32; while remainder_coeffs.len() >= divisor_coeffs.len() { - let lead_term = remainder_coeffs.last().unwrap().clone() * divisor_lead_inv.clone(); + let lead_term = remainder_coeffs.last().unwrap().mul_ref(&divisor_lead_inv); let deg_diff = remainder_coeffs.len() - divisor_coeffs.len(); quotient[deg_diff] = lead_term.clone(); for i in 0..divisor_coeffs.len() { - remainder_coeffs[deg_diff + i] = remainder_coeffs[deg_diff + i].clone() - - (lead_term.clone() * divisor_coeffs[i].clone()); + remainder_coeffs[deg_diff + i] = remainder_coeffs[deg_diff + i] + .sub_ref(&(lead_term.mul_ref(&divisor_coeffs[i]))); } remainder_coeffs = Self::trim_trailing_zeros(remainder_coeffs); - i += 1; } - Polynomial { - poly: Self::trim_trailing_zeros(quotient), - var: self.var.clone(), - } + ( + Polynomial { + coef: Self::trim_trailing_zeros(quotient), + }, + Polynomial { + coef: remainder_coeffs, + }, + ) + } +} + +impl Div for Polynomial { + type Output = Self; + + fn div(self, other: Polynomial) -> Polynomial { + self.div_rem_ref(&other).0 } } ``` ### Corollary +--- + *Let \\(f \in K[x]\\) be a non-zero polynomial, and \\(a \in K\\) such that \\(f(a) = 0\\). Then, there exists a polynomial \\(q \in K[x]\\) such that \\(f(x) = (x - a)q(x)\\). In other words, \\((x - a)\\) is a factor of \\(f(x)\\).* +--- + **Example:** Let \\(f(x) = x^2 + 1 \in (\mathbb{Z}/2\mathbb{Z})[x]\\). We have \\(f(1) = 1^2 + 1 = 0\\) in \\(\mathbb{Z}/2\mathbb{Z}\\), and indeed: \\(x^2 + 1 = (x - 1)^2 = x^2 - 2x + 1 = x^2 + 1\\) in \\((\mathbb{Z}/2\mathbb{Z})[x]\\) ### Theorem: Lagrange Interpolation +--- + *A \\(n\\)-degre polynomial \\(P(x)\\) that goes through different \\(n + 1\\) points \\(\\{(x_1, y_1), (x_2, y_2), \cdots (x_{n + 1}, y_{n + 1})\\}\\) is uniquely represented as follows:* -\begin{align*} +\\[ P(x) = \sum^{n+1}_{i=1} y_i \frac{f_i(x)}{f_i(x_i)} -\end{align*} +\\] *, where \\(f_i(x) = \prod_{k \neq i} (x - x_k)\\)* +--- + **Proof** TBD -For example, the quadratic polynomial that goes through \\(\\{(1, 0), (2, 3), (3, 8)\\}\\) is as follows: +**Example:** The quadratic polynomial that goes through \\(\\{(1, 0), (2, 3), (3, 8)\\}\\) is as follows: \begin{align*} P(x) = 0 \frac{(x - 2)(x - 3)}{(1 - 2) (1 - 3)} + 3 \frac{(x - 1)(x - 3)}{(2 - 1) (2 - 3)} + 8 \frac{(x - 1)(x - 2)}{(3 - 1) (3 - 2)} = x^{2} - 1 @@ -188,32 +252,33 @@ For example, the quadratic polynomial that goes through \\(\\{(1, 0), (2, 3), (3 Note that Lagrange interpolation finds the lowest degree of interpolating polynomial for the given vector. +**Implementation:** + ```rust -pub fn interpolate(x_values: &[F], y_values: &[F]) -> Polynomial { - let mut lagrange_polys = vec![]; - let numerators = Polynomial::from_monomials(x_values); - - for j in 0..x_values.len() { - let mut denominator = F::one(None); - for i in 0..x_values.len() { - if i != j { - denominator = denominator * (x_values[j].clone() - x_values[i].clone()); +impl Polynomial { + pub fn interpolate(x_values: &[F], y_values: &[F]) -> Polynomial { + let mut lagrange_polys = vec![]; + let numerators = Polynomial::from_monomials(x_values); + + for j in 0..x_values.len() { + let mut denominator = F::one(); + for i in 0..x_values.len() { + if i != j { + denominator = denominator * (x_values[j].sub_ref(&x_values[i])); + } } + let cur_poly = numerators + .clone() + .div(Polynomial::from_monomials(&[x_values[j].clone()]) * denominator); + lagrange_polys.push(cur_poly); } - let cur_poly = numerators - .clone() - .div(Polynomial::from_monomials(&[x_values[j].clone()]).scalar_mul(&denominator)); - lagrange_polys.push(cur_poly); - } - let mut result = Polynomial { - poly: vec![], - var: "x".to_string(), - }; - for (j, lagrange_poly) in lagrange_polys.iter().enumerate() { - result = result + lagrange_poly.scalar_mul(&y_values[j]); + let mut result = Polynomial { coef: vec![] }; + for (j, lagrange_poly) in lagrange_polys.iter().enumerate() { + result = result + lagrange_poly.clone() * y_values[j].clone(); + } + result } - result } ``` diff --git a/book/src/number_theory/subsec4.md b/book/src/number_theory/subsec4.md index 416c828..b748233 100644 --- a/book/src/number_theory/subsec4.md +++ b/book/src/number_theory/subsec4.md @@ -16,11 +16,18 @@ We will now discuss the construction of finite fields with \\(p^n\\) elements, w - \\(X^2 + X + 1\\) is irreducible - \\(X^2 + 1 = (X + 1)^2\\) is reducible +**Implementation:** -To construct \\(GF(p^n)\\), we use an irreducible polynomial of degree \\(n\\) over \\(\mathbb{Z}/p\mathbb{Z}\\). +```rust +pub trait IrreduciblePoly: Debug + Clone + Hash { + fn modulus() -> &'static Polynomial; +} +``` ### Definition: Residue Class modulo a Polynomial +To construct \\(GF(p^n)\\), we use an irreducible polynomial of degree \\(n\\) over \\(\mathbb{Z}/p\mathbb{Z}\\). + --- *For \\(f, g \in (\mathbb{Z}/p\mathbb{Z})[X]\\), the residue class of \\(g \bmod f\\) is the set of all polynomials \\(h \in (\mathbb{Z}/p\mathbb{Z})[X]\\) such that \\(h \equiv g \pmod{f}\\). This class is denoted as:* @@ -38,6 +45,64 @@ To construct \\(GF(p^n)\\), we use an irreducible polynomial of degree \\(n\\) o These four residue classes form \\(GF(4)\\). +**Implementation:** + +```rust +impl>> + ExtendedFieldElement +{ + pub fn new(poly: Polynomial>) -> Self { + let result = Self { + poly: poly, + _phantom: PhantomData, + }; + result.reduce() + } + + fn reduce(&self) -> Self { + Self { + poly: &self.poly.reduce() % P::modulus(), + _phantom: PhantomData, + } + } + + pub fn degree(&self) -> isize { + P::modulus().degree() + } + + pub fn from_base_field(value: FiniteFieldElement) -> Self { + Self::new((Polynomial { coef: vec![value] }).reduce()).reduce() + } +} + +impl>> Field + for ExtendedFieldElement +{ + fn inverse(&self) -> Self { + let mut lm = Polynomial::>::one(); + let mut hm = Polynomial::zero(); + let mut low = self.poly.clone(); + let mut high = P::modulus().clone(); + + while !low.is_zero() { + let q = &high / &low; + let r = &high % &low; + let nm = hm - (&lm * &q); + high = low; + hm = lm; + low = r; + lm = nm; + } + + Self::new(hm * high.coef[0].inverse()) + } + + fn div_ref(&self, other: &Self) -> Self { + self.mul_ref(&other.inverse()) + } +} +``` + ### Theorem: --- @@ -52,9 +117,29 @@ This construction allows us to represent elements of \\(GF(p^n)\\) as polynomial **Example:** To construct \\(GF(8)\\), we can use the irreducible polynomial \\(f(X) = X^3 + X + 1\\) over \\(\mathbb{Z}/2\mathbb{Z}\\). The elements of \\(GF(8)\\) are represented by: -\[ \\{0, 1, X, X+1, X^2, X^2+1, X^2+X, X^2+X+1\\} \] +\\[ \\{0, 1, X, X+1, X^2, X^2+1, X^2+X, X^2+X+1\\} \\] For instance, multiplication in \\(GF(8)\\): -\[ (X^2 + 1)(X + 1) = X^3 + X^2 + X + 1 \equiv X^2 \pmod{X^3 + X + 1} \] +\\[ (X^2 + 1)(X + 1) = X^3 + X^2 + X + 1 \equiv X^2 \pmod{X^3 + X + 1} \\] + +**Implementation:** + +```rust +impl>> Ring + for ExtendedFieldElement +{ + fn add_ref(&self, other: &Self) -> Self { + Self::new(&self.poly + &other.poly) + } + + fn mul_ref(&self, other: &Self) -> Self { + Self::new(&self.poly * &other.poly) + } + + fn sub_ref(&self, other: &Self) -> Self { + Self::new(&self.poly - &other.poly) + } +} +``` ### Lemma: Schwartz - Zippel Lemma diff --git a/book/src/number_theory/subsec5.md b/book/src/number_theory/subsec5.md index a8cfa4c..26f835b 100644 --- a/book/src/number_theory/subsec5.md +++ b/book/src/number_theory/subsec5.md @@ -14,13 +14,19 @@ --- +**Implementation:** + ```rust -use crate::modules::field::Field; +pub trait EllipticCurve: Debug + Clone + PartialEq { + fn get_a() -> BigInt; + fn get_b() -> BigInt; +} #[derive(Debug, Clone, PartialEq)] -pub struct EllipticCurve { - pub a: F, - pub b: F, +pub struct EllipticCurvePoint { + pub x: Option, + pub y: Option, + _phantom: PhantomData, } ``` @@ -43,12 +49,21 @@ The point at infinity denoted \\(\mathcal{O}\\), is a special point on the ellip --- +**Implementation:** + ```rust -#[derive(Debug, Clone, PartialEq)] -pub struct EllipticCurvePoint { - pub x: Option, - pub y: Option, - pub curve: EllipticCurve, +impl EllipticCurvePoint { + pub fn point_at_infinity() -> Self { + EllipticCurvePoint { + x: None, + y: None, + _phantom: PhantomData, + } + } + + pub fn is_point_at_infinity(&self) -> bool { + self.x.is_none() || self.y.is_none() + } } ``` @@ -72,78 +87,45 @@ pub struct EllipticCurvePoint { --- -Example: On \\(E: y^2 = x^3 + 2x + 3\\) over \\(\mathbb{F}_{7}\\), let \\(P = (5, 1)\\) and \\(Q = (4, 4)\\). Then, \\(P + Q = (0, 5)\\), where \\(\lambda = \frac{1 - 4}{5 - 4} \equiv 4 \bmod 7\\). +**Example:** On \\(E: y^2 = x^3 + 2x + 3\\) over \\(\mathbb{F}_{7}\\), let \\(P = (5, 1)\\) and \\(Q = (4, 4)\\). Then, \\(P + Q = (0, 5)\\), where \\(\lambda = \frac{1 - 4}{5 - 4} \equiv 4 \bmod 7\\). + +**Implementation:** ```rust -impl EllipticCurvePoint { - fn new(x: F, y: F, curve: EllipticCurve) -> Self { - EllipticCurvePoint { - x: Some(x), - y: Some(y), - curve: curve, +impl EllipticCurvePoint { + pub fn add_ref(&self, other: &Self) -> Self { + if self.is_point_at_infinity() { + return other.clone(); + } + if other.is_point_at_infinity() { + return self.clone(); } - } - pub fn point_at_infinity(curve: EllipticCurve) -> Self { - EllipticCurvePoint { - x: None, - y: None, - curve: curve, + if self.x == other.x && self.y == other.y { + return self.double(); + } else if self.x == other.x { + return Self::point_at_infinity(); } - } - pub fn is_point_at_infinity(&self) -> bool { - self.x.is_none() || self.y.is_none() - } + let slope = self.line_slope(&other); + let x1 = self.x.as_ref().unwrap(); + let y1 = self.y.as_ref().unwrap(); + let x2 = other.x.as_ref().unwrap(); + let y2 = other.y.as_ref().unwrap(); - pub fn line_slope(&self, other: Self) -> F { - let x1 = self.x.clone().unwrap(); - let y1 = self.y.clone().unwrap(); - let x2 = other.x.clone().unwrap(); - let y2 = other.y.clone().unwrap(); + let new_x = slope.mul_ref(&slope).sub_ref(&x1).sub_ref(&x2); + let new_y = ((-slope.clone()).mul_ref(&new_x)) + (&slope.mul_ref(&x1).sub_ref(&y1)); + assert!(new_y == -slope.clone() * &new_x + slope.mul_ref(&x2).sub_ref(&y2)); - if self.x.clone() == other.x.clone() { - ((x1.clone() * x1.clone()) * (3_i64) + self.curve.a.clone()) / (y1.clone() * (2_i64)) - } else { - (y2.clone() - y1.clone()) / (x2.clone() - x1.clone()) - } + Self::new(new_x, new_y) } } -impl Add for EllipticCurvePoint { +impl Add for EllipticCurvePoint { type Output = Self; fn add(self, other: Self) -> Self { - if self.is_point_at_infinity() { - return other; - } - if other.is_point_at_infinity() { - return self; - } - - let m = self.line_slope(other.clone()); - - if self.x == other.x { - if self.y != other.y { - return EllipticCurvePoint::point_at_infinity(self.curve.clone()); - } else { - let x1 = self.x.clone().unwrap(); - let y1 = self.y.clone().unwrap(); - - let x3 = m.clone() * m.clone() - x1.clone() - x1.clone(); - let y3 = m * (x1 - x3.clone()) - y1; - - return EllipticCurvePoint::new(x3, y3, self.curve.clone()); - } - } else { - let x1 = self.x.clone().unwrap(); - let y1 = self.y.clone().unwrap(); - let x2 = other.x.clone().unwrap(); - let x3 = m.clone() * m.clone() - x1.clone() - x2.clone(); - let y3 = m * (x1 - x3.clone()) - y1; - - return EllipticCurvePoint::new(x3, y3, self.curve.clone()); - } + self.add_ref(&other) } } ``` @@ -277,11 +259,10 @@ In other words, we can view \\(K[E]\\) as a ring representing all polynomial fun - Quadratic functions: \\(X^2, XY, Y^2 (= X^3 - X), \ldots\\) - Higher-degree functions: \\(X^3, X^2Y, XY^2 (= X^4 - X^2), \ldots\\) +### Definition: Function Field of an Elliptic Curve Then, the function field is defined as follows: -### Definition: Function Field of an Elliptic Curve - --- *Let \\(E: y^2 = x^3 + ax + b\\) be an elliptic curve over a field \\(K\\). The **function field** of \\(E\\), denoted \\(K(E)\\), is defined as:* @@ -294,7 +275,7 @@ Then, the function field is defined as follows: \\(K(E)\\) can be viewed as the field of all rational functions on \\(E\\). -### Definition: Zero of a Function +### Definition: Zero/Pole of a Function --- @@ -302,8 +283,6 @@ Then, the function field is defined as follows: --- -### Definition: Pole of a Function - --- *Let \\(h \in K(E)\\) be a non-zero function. A point \\(P \in E\\) is called a **pole** of \\(h\\) if \\(h\\) is not defined at \\(P\\) or, equivalently if \\(1/h\\) has a zero at \\(P\\).* @@ -353,7 +332,7 @@ We now introduce a powerful tool for analyzing functions on elliptic curves: the --- -Note that this \\(\sum_{P \in E}\\) is a symbolic summation, and we do not calculate the concrete value of a divisor. +Note that this \\(\sum_{P \in E}\\) is a symbolic summation, and we do not calculate the concrete numerical value of a divisor. **Example** Consider the elliptic curve \\(E: Y^2 = X^3 - X\\) over \\(\mathbb{Q}\\). @@ -362,10 +341,10 @@ Note that this \\(\sum_{P \in E}\\) is a symbolic summation, and we do not calcu - For \\(h = \frac{X-1}{Y}\\), we have \\(div(h) = [(1,0)] - [(-1,0)]\\). -The concept of divisors can be extended to the elliptic curve itself: - ### Definition: Divisor on an Elliptic Curve +The concept of divisors can be extended to the elliptic curve itself: + --- *A **divisor** \\(D\\) on an elliptic curve \\(E\\) is a formal sum* @@ -385,7 +364,8 @@ The concept of divisors can be extended to the elliptic curve itself: To quantify the properties of divisors, we introduce two important metrics: -### Definition: Degree of a Divisor + +### Definition: Degree/Sum of a Divisor --- @@ -396,8 +376,6 @@ To quantify the properties of divisors, we introduce two important metrics: --- -### Definition: Sum of a Divisor - --- *The **sum** of a divisor \\(D = \sum_{P \in E} n_P [P]\\) is defined as:* @@ -415,17 +393,16 @@ To quantify the properties of divisors, we introduce two important metrics: - \\(deg(D_3) = 4 - 4 = 0\\) - \\(Sum(D_2) = (1,0) + (-1,0) - 2\mathcal{O} = \mathcal{O}\\) (since \\((1,0)\\) and \\((-1,0)\\) are 2-torsion points) +### Theorem The following theorem characterizes divisors of functions and provides a criterion for when a divisor is the divisor of a function: -### Theorem - --- *Let \\(E\\) be an elliptic curve over a field \\(K\\).* -- *If \\(f, f' \in K(E)\\) are non-zero rational functions on \\(E\\) with \\(div(f) = div(f')\\), then there exists a non-zero constant \\(c \in K^*\\) such that \\(f = cf'\\).* +- *If \\(f, f' \in K(E)\\) are non-zero rational functions on \\(E\\) with \\(div(f) = div(f')\\), then there exists a non-zero constant \\(c \in K^\*\\) such that \\(f = cf'\\).* - *A divisor \\(D\\) on \\(E\\) is the divisor of a rational function on \\(E\\) if and only if \\(deg(D) = 0\\) and \\(Sum(D) = \mathcal{O}\\).* @@ -434,192 +411,4 @@ The following theorem characterizes divisors of functions and provides a criteri **Example** On \\(E: Y^2 = X^3 - X\\): - The function \\(f = \frac{Y}{X}\\) has \\(div(f) = [(1,0)] + [(-1,0)] - 2[(0,0)]\\). Note that \\(deg(div(f)) = 0\\) and \\(Sum(div(f)) = (1,0) + (-1,0) - 2(0,0) = \mathcal{O}\\). -- The divisor \\(D = 2[(1,1)] - [(2,\sqrt{6})] - [(0,0)]\\) has \\(deg(D) = 0\\), but \\(Sum(D) \neq \mathcal{O}\\). Therefore, \\(D\\) is not the divisor of any rational function on \\(E\\). - - -### Definition: Pairing - ---- - -*Let \\(G_1\\) and \\(G_2\\) be cyclic groups under addition, both of prime order \\(p\\), with generators \\(P\\) and \\(Q\\) respectively:* - -\begin{align} - G_1 &= \\{0, P, 2P, ..., (p-1)P\\} \\ - G_2 &= \\{0, Q, 2Q, ..., (p-1)Q\\} -\end{align} - -*Let \\(G_T\\) be a cyclic group under multiplication, also of order \\(p\\). A pairing is a map \\(e: G_1 \times G_2 \rightarrow G_T\\) that satisfies the following bilinear property:* - -\begin{equation} - e(aP, bQ) = e(P, Q)^{ab} -\end{equation} *for all \\(a, b \in \mathbb{Z}_p\\).* - ---- - -Imagine \\(G_1\\) represents length, \\(G_2\\) represents width, and \\(G_T\\) represents area. The pairing function \\(e\\) is like calculating the area: If you double the length and triple the width, the area becomes six times larger: \\(e(2P, 3Q) = e(P, Q)^{6}\\) - -The Weil pairing is one of the bilinear pairings for elliptic curves. We begin with its formal definition. - -### Definition: The Weil Pairing - ---- - -*Let \\(E\\) be an elliptic curve and \\(n\\) be a positive integer. For points \\(P, Q \in E[n]\\), where \\(E[n]\\) denotes the \\(n\\)-torsion subgroup of \\(E\\), we define the Weil pairing \\(e_n(P, Q)\\) as follows:* - -Let \\(f_P\\) and \\(f_Q\\) be rational functions on \\(E\\) satisfying: -\begin{align} - div(f_P) &= n[P] - n[\mathcal{O}] \\ - div(f_Q) &= n[Q] - n[\mathcal{O}] -\end{align} - -*Then, for an arbitrary point \\(S \in E\\) such that \\(S \notin \\{\mathcal{O}, P, -Q, P-Q\\}\\), the Weil pairing is given by:* - -\begin{equation} -e_n(P, Q) = \frac{f_P(Q + S)}{f_P(S)} /\ \frac{f_Q(P - S)}{f_Q(-S)} -\end{equation} - ---- - -We introduce a crucial theorem about a specific rational function on elliptic curves to construct the functions required for the Weil pairing. - -### Theorem - ---- - -*Let \\(E\\) be an elliptic curve over a field \\(K\\), and let \\(P = (x_P, y_P)\\) and \\(Q = (x_Q, y_Q)\\) be non-zero points on \\(E\\). Define \\(\lambda\\) as:* - -\begin{equation} - \lambda = \begin{cases} - \hbox{slope of the line through \\(P\\) and \\(Q\\)} &\quad \hbox{if \\(P \neq Q\\)} \\\\ - \hbox{slope of the tangent line to \\(E\\) at \\(P\\)} &\quad \hbox{if \\(P = Q\\)} \\\\ - \infty &\quad \hbox{if the line is vertical} - \end{cases} -\end{equation} - -*Then, the function \\(g_{P,Q}: E \to K\\) defined by:* -\begin{equation} -g_{P,Q} = \begin{cases} -\frac{y - y_P - \lambda(x - x_P)}{x + x_P + x_Q - \lambda^2} &\quad \hbox{if } \lambda \neq \infty \\\\ -x - x_P &\quad \hbox{if } \lambda = \infty -\end{cases} -\end{equation} *has the following divisor:* - -\begin{equation} -div(g_{P,Q}) = [P] + [Q] - [P + Q] - [\mathcal{O}] -\end{equation} - ---- - -**Proof:** We consider two cases based on the value of \\(\lambda\\). - -Case 1: \\(\lambda \neq \infty\\) - -Let \\(y = \lambda x + v\\) be the line through \\(P\\) and \\(Q\\) (or the tangent line at \\(P\\) if \\(P = Q\\)). This line intersects \\(E\\) at three points: \\(P\\), \\(Q\\), and \\(-P-Q\\). Thus, -\begin{equation} -div(y - \lambda x - v) = [P] + [Q] + [-P - Q] - 3[\mathcal{O}] -\end{equation} -Vertical lines intersect \\(E\\) at points and their negatives, so: -\begin{equation} -div(x - x_{P+Q}) = [P + Q] + [-P - Q] - 2[\mathcal{O}] -\end{equation} -It follows that \\(g_{P,Q} = \frac{y - \lambda x - v}{x - x_{P+Q}}\\) has the desired divisor. - -Case 2: \\(\lambda = \infty\\) - -In this case, \\(P + Q = \mathcal{O}\\), so we want \\(g_{P,Q}\\) to have divisor \\([P] + [-P] - 2[\mathcal{O}]\\). The function \\(x - x_P\\) has this divisor. -\end{proof} - -### Theorem: Miller's Algorithm - ---- - -*Let \\(m \geq 1\\) and write its binary expansion as:* -\begin{equation} -m = m_0 + m_1 \cdot 2 + m_2 \cdot 2^2 + \cdots + m_{n-1} \cdot 2^{n-1} -\end{equation} -*where \\(m_i \in \\{0, 1\\}\\) and \\(m_{n-1} \neq 0\\).* - -*The following algorithm, using the function \\(g_{P,Q}\\) defined in the previous theorem, returns a function \\(f_P\\) whose divisor satisfies:* - -\begin{equation} - div(f_P) = m[P] - m[P] - (m - 1)[\mathcal{O}] -\end{equation} - -=========================== - -**Miller's Algorithm** - -1. Set \\(T = P\\) and \\(f = 1\\) -2. **For** \\(i \gets n-2 \cdots 0\\) **do** -3.     Set \\(f = f^2 \cdot g_{T, T}\\) -4.     Set \\(T = 2T\\) -5.     **If** \\(m_i = 1\\) **then** -6.         Set \\(f = f \cdot g_{T, P}\\) -7.         Set \\(T = T + P\\) -8.     **End if** -9. **End for** -10. **Return** \\(f\\) - -=========================== - ---- - -**Proof:** TBD - -```rust -pub fn get_lambda( - p: EllipticCurvePoint, - q: EllipticCurvePoint, - r: EllipticCurvePoint, -) -> F { - let p_x = p.x.clone().unwrap(); - let p_y = p.y.clone().unwrap(); - let q_x = q.x.clone().unwrap(); - // let q_y = q.y.clone().unwrap(); - let r_x = r.x.clone().unwrap(); - let r_y = r.y.clone().unwrap(); - - if (p == q && p_y.clone() == F::zero()) || (p != q && p_x.clone() == q_x.clone()) { - return r_x.clone() - p_x.clone(); - } - let slope = p.line_slope(q.clone()); - let numerator = r_y.clone() - p_y.clone() - slope.clone() * (r_x.clone() - p_x.clone()); - let denominator = r_x.clone() + p_x.clone() + q_x.clone() - slope.clone() * slope.clone(); - return numerator / denominator; -} - -pub fn miller(p: EllipticCurvePoint, q: EllipticCurvePoint, m: BigInt) -> F { - if p == q { - F::one(); - } - - let mut f = F::one(); - let mut t = p.clone(); - - for i in (1..m.bits()).rev() { - f = (f.clone() * f.clone()) * (get_lambda(t.clone(), t.clone(), q.clone())); - t = t.clone() + t.clone(); - if m.bit(i) { - f = f * (get_lambda(t.clone(), p.clone(), q.clone())); - t = t.clone() + p.clone(); - } - } - - f -} - -pub fn weil_pairing( - p: EllipticCurvePoint, - q: EllipticCurvePoint, - m: BigInt, - s: Option>, -) -> F { - let s_value = s.unwrap(); - let fp_qs = miller(p.clone(), q.clone() + s_value.clone(), m.clone()); - let fp_s = miller(p.clone(), s_value.clone(), m.clone()); - let fq_ps = miller(q.clone(), p.clone() - s_value.clone(), m.clone()); - let fq_s = miller(q.clone(), -s_value.clone(), m.clone()); - - return (fp_qs / fp_s) / (fq_ps / fq_s); -} -``` +- The divisor \\(D = 2[(1,1)] - [(2,\sqrt{6})] - [(0,0)]\\) has \\(deg(D) = 0\\), but \\(Sum(D) \neq \mathcal{O}\\). Therefore, \\(D\\) is not the divisor of any rational function on \\(E\\). \ No newline at end of file diff --git a/book/src/number_theory/subsec6.md b/book/src/number_theory/subsec6.md index 2bccff1..a61ed85 100644 --- a/book/src/number_theory/subsec6.md +++ b/book/src/number_theory/subsec6.md @@ -1,53 +1,182 @@ -# Assumptions +# Pairing -### Assumption: Discrete Logarithm Problem +### Definition: Pairing --- -*Let \\(G\\) be a finite cyclic group of order \\(n\\), with \\(\gamma\\) as its generator and \\(1\\) as the identity element. For any element \\(\alpha \in G\\), there is currently no known efficient (polynomial-time) algorithm to compute the smallest non-negative integer \\(x\\) such that \\(\alpha = \gamma^{x}\\).* +*Let \\(G_1\\) and \\(G_2\\) be cyclic groups under addition, both of prime order \\(p\\), with generators \\(P\\) and \\(Q\\) respectively:* + +\begin{align} + G_1 &= \\{0, P, 2P, ..., (p-1)P\\} \\ + G_2 &= \\{0, Q, 2Q, ..., (p-1)Q\\} +\end{align} + +*Let \\(G_T\\) be a cyclic group under multiplication, also of order \\(p\\). A pairing is a map \\(e: G_1 \times G_2 \rightarrow G_T\\) that satisfies the following bilinear property:* + +\begin{equation} + e(aP, bQ) = e(P, Q)^{ab} +\end{equation} *for all \\(a, b \in \mathbb{Z}_p\\).* --- -The Discrete Logarithm Problem can be thought of as a one-way function. It's easy to compute \\(g^{x}\\) given \\(g\\) and \\(x\\), but it's computationally difficult to find \\(x\\) given \\(g\\) and \\(g^{x}\\). +Imagine \\(G_1\\) represents length, \\(G_2\\) represents width, and \\(G_T\\) represents area. The pairing function \\(e\\) is like calculating the area: If you double the length and triple the width, the area becomes six times larger: \\(e(2P, 3Q) = e(P, Q)^{6}\\) + +### Definition: The Weil Pairing -### Assumption: Elliptic Curve Discrete Logarithm Problem +The Weil pairing is one of the bilinear pairings for elliptic curves. We begin with its formal definition. --- -*Let \\(E\\) be an elliptic curve defined over a finite field \\(\mathbb{F}_q\\), where \\(q\\) is a prime power. Let \\(P\\) be a point on \\(E\\) of point order \\(n\\), and let \\(\langle P \rangle\\) be the cyclic subgroup of \\(E\\) generated by \\(P\\). For any element \\(Q \in \langle P \rangle\\), there is currently no known efficient (polynomial-time) algorithm to compute the unique integer \\(k\\), \\(0 \leq k < n\\), such that \\(Q = kP\\).* +*Let \\(E\\) be an elliptic curve and \\(n\\) be a positive integer. For points \\(P, Q \in E[n]\\), where \\(E[n]\\) denotes the \\(n\\)-torsion subgroup of \\(E\\), we define the Weil pairing \\(e_n(P, Q)\\) as follows:* + +Let \\(f_P\\) and \\(f_Q\\) be rational functions on \\(E\\) satisfying: +\begin{align} + div(f_P) &= n[P] - n[\mathcal{O}] \\ + div(f_Q) &= n[Q] - n[\mathcal{O}] +\end{align} + +*Then, for an arbitrary point \\(S \in E\\) such that \\(S \notin \\{\mathcal{O}, P, -Q, P-Q\\}\\), the Weil pairing is given by:* + +\begin{equation} +e_n(P, Q) = \frac{f_P(Q + S)}{f_P(S)} /\ \frac{f_Q(P - S)}{f_Q(-S)} +\end{equation} --- -This assumption is an elliptic curve version of the Discrete Logarithm Problem. +We introduce a crucial theorem about a specific rational function on elliptic curves to construct the functions required for the Weil pairing. -### Assumption: Knowledge of Exponent Assumption +### Theorem --- -Let \\(G\\) be a cyclic group of prime order \\(q\\) with generator \\(g \in G\\). For any probabilistic polynomial-time algorithm \\(\mathcal{A}\\) that outputs: +*Let \\(E\\) be an elliptic curve over a field \\(K\\), and let \\(P = (x_P, y_P)\\) and \\(Q = (x_Q, y_Q)\\) be non-zero points on \\(E\\). Define \\(\lambda\\) as:* \begin{equation} -\mathcal{A}(g, g^x) = (h, h') \quad s.t. \quad h' = h^x + \lambda = \begin{cases} + \hbox{slope of the line through \\(P\\) and \\(Q\\)} &\quad \hbox{if \\(P \neq Q\\)} \\\\ + \hbox{slope of the tangent line to \\(E\\) at \\(P\\)} &\quad \hbox{if \\(P = Q\\)} \\\\ + \infty &\quad \hbox{if the line is vertical} + \end{cases} \end{equation} -, there exists an efficient extractor \\(\mathcal{E}\\) such that: + +*Then, the function \\(g_{P,Q}: E \to K\\) defined by:* +\begin{equation} +g_{P,Q} = \begin{cases} +\frac{y - y_P - \lambda(x - x_P)}{x + x_P + x_Q - \lambda^2} &\quad \hbox{if } \lambda \neq \infty \\\\ +x - x_P &\quad \hbox{if } \lambda = \infty +\end{cases} +\end{equation} *has the following divisor:* + \begin{equation} -\mathcal{E}(\mathcal{A}, g, g^x) = y \quad s.t. \quad h = g^y +div(g_{P,Q}) = [P] + [Q] - [P + Q] - [\mathcal{O}] \end{equation} --- -This assumption states that if \\(\mathcal{A}\\) can compute the pair \\((g^y, g^{xy})\\) from \\((g, g^x)\\), then \\(\mathcal{A}\\) must "know" the value \\(y\\), in the sense that \\(\mathcal{E}\\) can extract \\(y\\) from \\(\mathcal{A}\\)'s internal state. -The Knowledge of Exponent Assumption is useful for constructing verifiable exponential calculation algorithms. Consider a scenario where Alice has a secret value \\(a\\), and Bob has a secret value \\(b\\). Bob wants to obtain \\(g^{ab}\\). This can be achieved through the following protocol: +**Proof:** We consider two cases based on the value of \\(\lambda\\). -**Verifiable Exponential Calculation Algorithm** +Case 1: \\(\lambda \neq \infty\\) -1. Bob sends \\((g, g'=g^{b})\\) to Alice -2. Alice sends \\((h=g^{a}, h'=g'^{a})\\) to Bob -3. Bob checks \\(h^{b} = h'\\). +Let \\(y = \lambda x + v\\) be the line through \\(P\\) and \\(Q\\) (or the tangent line at \\(P\\) if \\(P = Q\\)). This line intersects \\(E\\) at three points: \\(P\\), \\(Q\\), and \\(-P-Q\\). Thus, +\begin{equation} +div(y - \lambda x - v) = [P] + [Q] + [-P - Q] - 3[\mathcal{O}] +\end{equation} +Vertical lines intersect \\(E\\) at points and their negatives, so: +\begin{equation} +div(x - x_{P+Q}) = [P + Q] + [-P - Q] - 2[\mathcal{O}] +\end{equation} +It follows that \\(g_{P,Q} = \frac{y - \lambda x - v}{x - x_{P+Q}}\\) has the desired divisor. -Thanks to the Discrete Logarithm Assumption and the Knowledge of Exponent Assumption, the following properties hold: +Case 2: \\(\lambda = \infty\\) + +In this case, \\(P + Q = \mathcal{O}\\), so we want \\(g_{P,Q}\\) to have divisor \\([P] + [-P] - 2[\mathcal{O}]\\). The function \\(x - x_P\\) has this divisor. +\end{proof} + +### Theorem: Miller's Algorithm + +--- + +*Let \\(m \geq 1\\) and write its binary expansion as:* +\begin{equation} +m = m_0 + m_1 \cdot 2 + m_2 \cdot 2^2 + \cdots + m_{n-1} \cdot 2^{n-1} +\end{equation} +*where \\(m_i \in \\{0, 1\\}\\) and \\(m_{n-1} \neq 0\\).* + +*The following algorithm, using the function \\(g_{P,Q}\\) defined in the previous theorem, returns a function \\(f_P\\) whose divisor satisfies:* + +\begin{equation} + div(f_P) = m[P] - m[P] - (m - 1)[\mathcal{O}] +\end{equation} + +=========================== + +**Miller's Algorithm** + +1. Set \\(T = P\\) and \\(f = 1\\) +2. **For** \\(i \gets n-2 \cdots 0\\) **do** +3.     Set \\(f = f^2 \cdot g_{T, T}\\) +4.     Set \\(T = 2T\\) +5.     **If** \\(m_i = 1\\) **then** +6.         Set \\(f = f \cdot g_{T, P}\\) +7.         Set \\(T = T + P\\) +8.     **End if** +9. **End for** +10. **Return** \\(f\\) + +=========================== + +--- -- Bob cannot derive \\(a\\) from \\((h, h')\\). -- Alice cannot derive \\(b\\) from \\((g, g')\\). -- Alice cannot generate \\((t, t')\\) such that \\(t \neq h\\) and \\(t^{b} = t'\\). -- If \\(h^{b} = h'\\), Bob can conclude that \\(h\\) is the power of \\(g\\). +**Proof:** TBD + +**Implementation:** + +```rust +pub fn get_lambda( + p: &EllipticCurvePoint, + q: &EllipticCurvePoint, + r: &EllipticCurvePoint, +) -> F { + let p_x = p.x.as_ref().unwrap(); + let p_y = p.y.as_ref().unwrap(); + let q_x = q.x.as_ref().unwrap(); + // let q_y = q.y.clone().unwrap(); + let r_x = r.x.as_ref().unwrap(); + let r_y = r.y.as_ref().unwrap(); + + if (p == q && *p_y == F::zero()) || (p != q && *p_x == *q_x) { + return r_x.sub_ref(&p_x); + } + let slope = p.line_slope(&q); + let numerator = (r_y.sub_ref(&p_y)).sub_ref(&slope.mul_ref(&(r_x.sub_ref(&p_x)))); + let denominator = r_x + .add_ref(&p_x) + .add_ref(&q_x) + .sub_ref(&slope.mul_ref(&slope)); + return numerator / denominator; +} + +pub fn miller( + p: &EllipticCurvePoint, + q: &EllipticCurvePoint, + m: &BigInt, +) -> (F, EllipticCurvePoint) { + if p == q { + return (F::one(), p.clone()); + } + + let mut f = F::one(); + let mut t = p.clone(); + + for i in (0..(m.bits() - 1)).rev() { + f = (f.mul_ref(&f)) * (get_lambda(&t, &t, &q)); + t = t.add_ref(&t); + if m.bit(i) { + f = f * (get_lambda(&t, &p, &q)); + t = t.add_ref(&p); + } + } + + (f, t) +} +``` diff --git a/book/src/number_theory/subsec7.md b/book/src/number_theory/subsec7.md new file mode 100644 index 0000000..627df80 --- /dev/null +++ b/book/src/number_theory/subsec7.md @@ -0,0 +1,53 @@ +# Assumptions + +### Assumption: Discrete Logarithm Problem + +--- + +*Let \\(G\\) be a finite cyclic group of order \\(n\\), with \\(\gamma\\) as its generator and \\(1\\) as the identity element. For any element \\(\alpha \in G\\), there is currently no known efficient (polynomial-time) algorithm to compute the smallest non-negative integer \\(x\\) such that \\(\alpha = \gamma^{x}\\).* + +--- + +The Discrete Logarithm Problem can be thought of as a one-way function. It's easy to compute \\(g^{x}\\) given \\(g\\) and \\(x\\), but it's computationally difficult to find \\(x\\) given \\(g\\) and \\(g^{x}\\). + +### Assumption: Elliptic Curve Discrete Logarithm Problem + +--- + +*Let \\(E\\) be an elliptic curve defined over a finite field \\(\mathbb{F}_q\\), where \\(q\\) is a prime power. Let \\(P\\) be a point on \\(E\\) of point order \\(n\\), and let \\(\langle P \rangle\\) be the cyclic subgroup of \\(E\\) generated by \\(P\\). For any element \\(Q \in \langle P \rangle\\), there is currently no known efficient (polynomial-time) algorithm to compute the unique integer \\(k\\), \\(0 \leq k < n\\), such that \\(Q = kP\\).* + +--- + +This assumption is an elliptic curve version of the Discrete Logarithm Problem. + +### Assumption: Knowledge of Exponent Assumption + +--- + +*Let \\(G\\) be a cyclic group of prime order \\(q\\) with generator \\(g \in G\\). For any probabilistic polynomial-time algorithm \\(\mathcal{A}\\) that outputs:* + +\begin{equation} +\mathcal{A}(g, g^x) = (h, h') \quad s.t. \quad h' = h^x +\end{equation} +*, there exists an efficient extractor \\(\mathcal{E}\\) such that:* +\begin{equation} +\mathcal{E}(\mathcal{A}, g, g^x) = y \quad s.t. \quad h = g^y +\end{equation} + +--- + +This assumption states that if \\(\mathcal{A}\\) can compute the pair \\((g^y, g^{xy})\\) from \\((g, g^x)\\), then \\(\mathcal{A}\\) must "know" the value \\(y\\), in the sense that \\(\mathcal{E}\\) can extract \\(y\\) from \\(\mathcal{A}\\)'s internal state. +The Knowledge of Exponent Assumption is useful for constructing verifiable exponential calculation algorithms. Consider a scenario where Alice has a secret value \\(a\\), and Bob has a secret value \\(b\\). Bob wants to obtain \\(g^{ab}\\). This can be achieved through the following protocol: + +**Verifiable Exponential Calculation Algorithm** + +1. Bob sends \\((g, g'=g^{b})\\) to Alice +2. Alice sends \\((h=g^{a}, h'=g'^{a})\\) to Bob +3. Bob checks \\(h^{b} = h'\\). + +Thanks to the Discrete Logarithm Assumption and the Knowledge of Exponent Assumption, the following properties hold: + +- Bob cannot derive \\(a\\) from \\((h, h')\\). +- Alice cannot derive \\(b\\) from \\((g, g')\\). +- Alice cannot generate \\((t, t')\\) such that \\(t \neq h\\) and \\(t^{b} = t'\\). +- If \\(h^{b} = h'\\), Bob can conclude that \\(h\\) is the power of \\(g\\). diff --git a/book/src/zksnark/subsec2.md b/book/src/zksnark/subsec2.md index 015388e..04562ec 100644 --- a/book/src/zksnark/subsec2.md +++ b/book/src/zksnark/subsec2.md @@ -1,6 +1,6 @@ # Arithmetization -The ultimate goal of Zero-Knowledge Proofs (ZKP) is to allow the prover to demonstrate their knowledge to the verifier without revealing any additional information. This knowledge typically involves solving complex problems, such as finding a secret input value that corresponds to a given public hash. ZKP protocols usually convert these statements into polynomial constraints. This process is often called \textit{arithmetization}. +The ultimate goal of Zero-Knowledge Proofs (ZKP) is to allow the prover to demonstrate their knowledge to the verifier without revealing any additional information. This knowledge typically involves solving complex problems, such as finding a secret input value that corresponds to a given public hash. ZKP protocols usually convert these statements into polynomial constraints. This process is often called **arithmetization**. To make the protocol flexible, we need to encode this knowledge in a specific format, and one common approach is using Boolean circuits. It's well-known that problems in P (those solvable in polynomial time) and NP (those where the solution can be verified in polynomial time) can be represented as Boolean circuits. This means adopting Boolean circuits allows us to handle both P and NP problems. @@ -15,7 +15,7 @@ There are many formats to represent arithmetic circuits, and one of the most pop An R1CS structure \\(\mathcal{S}\\) consists of: - Size bounds \\(m, d, \ell \in \mathbb{N}\\) where \\(d > \ell\\) -- Three matrices \\(O, L, R \in \mathbb{F}^{m\ times d}\\) with at most \\(\Omega(\max(m, d))\\) non-zero entries in total +- Three matrices \\(O, L, R \in \mathbb{F}^{m \times d}\\) with at most \\(\Omega(\max(m, d))\\) non-zero entries in total An R1CS instance includes a public input \\(p \in \mathbb{F}^\ell\\), while an R1CS witness is a vector \\(w \in \mathbb{F}^{d - \ell - 1}\\). @@ -38,9 +38,9 @@ The intuitive interpretation of each matrix is as follows: Let's consider a simple example where we want to prove \\(z = x \cdot y\\), with \\(z = 3690\\), \\(x = 82\\), and \\(y = 45\\). -- \textbf{Witness vector}: \\((1, z, x, y) = (1, 3690, 82, 45)\\) -- \textbf{Number of witnesses}: \\(m = 4\\) -- \textbf{Number of constraints}: \\(d = 1\\) +- **Witness vector**: \\((1, z, x, y) = (1, 3690, 82, 45)\\) +- **Number of witnesses**: \\(m = 4\\) +- **Number of constraints**: \\(d = 1\\) The R1CS constraint for \\(z = x \cdot y\\) is satisfied when: @@ -145,7 +145,7 @@ O &= \begin{bmatrix} \end{bmatrix} \end{align*} -## uadratic Arithmetic Program (QAP) +## Quadratic Arithmetic Program (QAP) Recall that the prover aims to demonstrate knowledge of a witness \\(w\\) without revealing it. This is equivalent to knowing a vector \\(a\\) that satisfies \\((L \cdot a) \circ (R \cdot a) = O \cdot a\\), where \\(\circ\\) denotes the Hadamard (element-wise) product. However, evaluating this equivalence directly requires \\(\Omega(d)\\) operations, where \\(d\\) is the number of rows. To improve efficiency, we can convert this matrix comparison to a polynomial comparison, leveraging the Schwartz-Zippel Lemma, which allows us to check polynomial equality with \\(\Omega(1)\\) evaluations. diff --git a/book/src/zksnark/subsec3.md b/book/src/zksnark/subsec3.md index d609a2f..4a50d5c 100644 --- a/book/src/zksnark/subsec3.md +++ b/book/src/zksnark/subsec3.md @@ -10,11 +10,11 @@ Assume \\(P(x)\\) has \\(n\\) roots, \\(a_1, a_2, \ldots, a_n \in \mathbb{F}\\), The Prover's objective is to convince the Verifier that \\(\mathcal{A}\\) knows a polynomial \\(H(x) = \frac{P(x)}{T(x)}\\). -### Naive Approach +## Naive Approach The simplest approach to prove that \\(\mathcal{A}\\) knows \\(H(x)\\) is as follows: -*Naive Approach* +**Protocol:** - *\\(\mathcal{B}\\) sends all possible values in \\(\mathbb{F}\\) to \\(\mathcal{A}\\).* - *\\(\mathcal{A}\\) computes and sends all possible outputs of \\(H(x)\\) and \\(P(x)\\).* @@ -22,19 +22,174 @@ The simplest approach to prove that \\(\mathcal{A}\\) knows \\(H(x)\\) is as fol This protocol is highly inefficient, requiring \\(\mathcal{O}(|\mathbb{F}|)\\) evaluations and communications. -### \\(+\\) Schwartz-Zippel Lemma +**Implementation:** + +```rust +pub struct Prover { + pub p: Polynomial, + pub t: Polynomial, + pub h: Polynomial, +} + +pub struct Verifier { + pub t: Polynomial, + pub known_roots: Vec, +} + +impl Prover { + pub fn new(p: Polynomial, t: Polynomial) -> Self { + let h = p.clone() / t.clone(); + Prover { p, t, h } + } + + pub fn compute_all_values(&self, modulus: i128) -> (HashMap, HashMap) { + let mut h_values = HashMap::new(); + let mut p_values = HashMap::new(); + + for i in 0..modulus { + let x = F::from_value(i); + h_values.insert(x.clone(), self.h.eval(&x)); + p_values.insert(x.clone(), self.p.eval(&x)); + } + + (h_values, p_values) + } +} + +impl Verifier { + pub fn new(known_roots: Vec) -> Self { + let t = Polynomial::from_monomials(&known_roots); + Verifier { t, known_roots } + } + + pub fn verify(&self, h_values: &HashMap, p_values: &HashMap) -> bool { + for (x, h_x) in h_values { + let t_x = self.t.eval(x); + let p_x = p_values.get(x).unwrap(); + if h_x.clone() * t_x != *p_x { + return false; + } + } + true + } +} + +pub fn naive_protocol(prover: &Prover, verifier: &Verifier, modulus: i128) -> bool { + // Step 1: Verifier sends all possible values (implicitly done by Prover computing all values) + + // Step 2: Prover computes and sends all possible outputs + let (h_values, p_values) = prover.compute_all_values(modulus); + + // Step 3: Verifier checks whether H(a)T(a) = P(a) holds for any a in F + verifier.verify(&h_values, &p_values) +} +``` + +## \\(+\\) Schwartz-Zippel Lemma Instead of evaluating polynomials at all values in \\(\mathbb{F}\\), we can leverage the Schwartz-Zippel Lemma: if \\(H(s) = \frac{P(s)}{T(s)}\\) or equivalently \\(H(s)T(s) = P(s)\\) for a random element \\(s\\), we can conclude that \\(H(x) = \frac{P(x)}{T(x)}\\) with high probability. Thus, the Prover \\(\mathcal{A}\\) only needs to send evaluations of \\(P(s)\\) and \\(H(s)\\) for a random input \\(s\\) received from \\(\mathcal{B}\\). -*\\(+\\) Schwartz-Zippel Lemma* +**Protocol:** - *\\(\mathcal{B}\\) draws random \\(s\\) from \\(\mathbb{F}\\) and sends it to \\(\mathcal{A}\\).* - *\\(\mathcal{A}\\) computes \\(h = H(s)\\) and \\(p = P(s)\\) and send them to \\(\mathcal{B}\\).* - *\\(\mathcal{B}\\) checks whether \\(p = t h\\), where \\(t\\) denotes \\(T(s)\\).* -This protocol is efficient, requiring only a constant number of evaluations and communications. However, it is vulnerable to a malicious prover who could send an arbitrary value \\(h'\\) and the corresponding \\(p'\\) such that \\(p' = h't\\). - -### \\(+\\) Discrete Logarithm Assumption +This protocol is efficient, requiring only a constant number of evaluations and communications. + +**Implementation:** + +```rust +pub struct Prover { + pub p: Polynomial, + pub t: Polynomial, + pub h: Polynomial, +} + +pub struct Verifier { + pub t: Polynomial, +} + +impl Prover { + pub fn new(p: Polynomial, t: Polynomial) -> Self { + let h = p.clone() / t.clone(); + Prover { p, t, h } + } + + pub fn compute_values(&self, s: &F) -> (F, F) { + let h_s = self.h.eval(s); + let p_s = self.p.eval(s); + (h_s, p_s) + } +} + +impl Verifier { + pub fn new(t: Polynomial) -> Self { + Verifier { t } + } + + pub fn generate_challenge(&self) -> F { + F::random_element(&[]) + } + + pub fn verify(&self, s: &F, h: &F, p: &F) -> bool { + let t_s = self.t.eval(s); + h.clone() * t_s == *p + } +} + +// Simulating a malicious prover +pub struct MaliciousProver { + t: Polynomial, +} + +impl MaliciousProver { + pub fn new(t: Polynomial) -> Self { + MaliciousProver { t } + } + + pub fn compute_malicious_values(&self, s: &F) -> (F, F) { + let h_prime = F::random_element(&[]); + let t_s = self.t.eval(s); + let p_prime = h_prime.clone() * t_s; + (h_prime, p_prime) + } +} + +pub fn schwartz_zippel_protocol(prover: &Prover, verifier: &Verifier) -> bool { + // Step 1: Verifier generates a random challenge + let s = verifier.generate_challenge(); + + // Step 2: Prover computes and sends h and p + let (h, p) = prover.compute_values(&s); + + // Step 3: Verifier checks whether p = t * h + verifier.verify(&s, &h, &p) +} +``` + +**Vulnerability:** + +However, it is vulnerable to a malicious prover who could send an arbitrary value \\(h'\\) and the corresponding \\(p'\\) such that \\(p' = h't\\). + +```rust +pub fn malicious_schwartz_zippel_protocol( + prover: &MaliciousProver, + verifier: &Verifier, +) -> bool { + // Step 1: Verifier generates a random challenge + let s = verifier.generate_challenge(); + + // Step 2: Malicious Prover computes and sends h' and p' + let (h_prime, p_prime) = prover.compute_malicious_values(&s); + + // Step 3: Verifier checks whether p' = t * h' + verifier.verify(&s, &h_prime, &p_prime) +} + +``` + +## \\(+\\) Discrete Logarithm Assumption To address this vulnerability, the Verifier must hide the randomly chosen input \\(s\\) from the Prover. This can be achieved using the discrete logarithm assumption: it is computationally hard to determine \\(s\\) from \\(\alpha\\), where \\(\alpha = g^s \bmod p\\). Thus, it's safe for the Verifier to send \\(\alpha\\), as the Prover cannot easily derive \\(s\\) from it. @@ -52,7 +207,7 @@ Instead of sending \\(s\\), the Verifier can send \\(g\\) and \\(\alpha_{i} = g^ Similarly, the Prover can evaluate \\(g^h = g^{H(s)}\\). The Verifier can then check \\(p = ht \iff g^p = (g^h)^t\\). -*\\(+\\) Discrete Logarithm Assumption* +**Protocol:** - *\\(\mathcal{B}\\) randomly draw \\(s\\) from \\(\mathbb{F}\\).* - *\\(\mathcal{B}\\) computes and sends \\(\\{\alpha, \alpha_2, ..., \alpha_{n}\\}\\), where \\(\alpha_i= g^{(s^{i})}\\).* @@ -61,9 +216,115 @@ Similarly, the Prover can evaluate \\(g^h = g^{H(s)}\\). The Verifier can then c This approach prevents the Prover from obtaining \\(s\\) or \\(t = T(s)\\), making it impossible to send fake \\((h', p')\\) such that \\(p' = h't\\). +```rust +pub struct Prover { + pub p: Polynomial, + pub t: Polynomial, + pub h: Polynomial, +} + +pub struct Verifier { + t: Polynomial, + s: F, + g: F, +} + +impl Prover { + pub fn new(p: Polynomial, t: Polynomial) -> Self { + let h = p.clone() / t.clone(); + Prover { p, t, h } + } + + pub fn compute_values(&self, alpha_powers: &[F]) -> (F, F) { + let g_p = self.p.eval_with_powers(alpha_powers); + let g_h = self.h.eval_with_powers(alpha_powers); + (g_p, g_h) + } +} + +impl Verifier { + pub fn new(t: Polynomial, generator: i128) -> Self { + let mut rng = rand::thread_rng(); + let s = F::from_value(rng.gen_bigint_range( + &BigInt::zero(), + &BigInt::from(std::u64::MAX), + )); + let g = F::from_value(generator); + Verifier { t, s, g } + } + + pub fn generate_challenge(&self, max_degree: usize) -> Vec { + let mut alpha_powers = vec![]; + for i in 0..(max_degree + 1) { + alpha_powers.push( + self.g + .pow(self.s.clone().pow(i.to_bigint().unwrap()).get_value()), + ); + } + alpha_powers + } + + pub fn verify(&self, u: &F, v: &F) -> bool { + let t_s = self.t.eval(&self.s); + u == &v.pow(t_s.get_value()) + } +} + +// Simulating a malicious prover +pub struct MaliciousProver { + t: Polynomial, +} + +impl MaliciousProver { + pub fn new(t: Polynomial) -> Self { + MaliciousProver { t } + } + + pub fn compute_malicious_values(&self, alpha_powers: &[F]) -> (F, F) { + let g_t = self.t.eval_with_powers(alpha_powers); + let z = F::random_element(&[]); + let g = &alpha_powers[0]; + let fake_v = g.pow(z.get_value()); + let fake_u = g_t.pow(z.get_value()); + (fake_u, fake_v) + } +} + +pub fn discrete_log_protocol(prover: &Prover, verifier: &Verifier) -> bool { + // Step 1 & 2: Verifier generates a challenge + let max_degree = prover.p.degree(); + let alpha_powers = verifier.generate_challenge(max_degree as usize); + + // Step 3: Prover computes and sends u = g^p and v = g^h + let (u, v) = prover.compute_values(&alpha_powers); + + // Step 4: Verifier checks whether u = v^t + verifier.verify(&u, &v) +} +``` + +**Vulnerability:** + However, this protocol still has a flaw. Since the Prover can compute \\(g^t = \alpha_{c_1}(\alpha_2)^{c_2}\cdots(\alpha_d)^{c_d}\\), they could send fake values \\(((g^{t})^{z}, g^{z})\\) instead of \\((g^p, g^h)\\) for an arbitrary value \\(z\\). The verifier's check would still pass, and they could not detect this deception. -### \\(+\\) Knowledge of Exponent Assumption +```rust +pub fn malicious_discrete_log_protocol( + prover: &MaliciousProver, + verifier: &Verifier, +) -> bool { + // Step 1 & 2: Verifier generates a challenge + let max_degree = prover.t.degree() as usize; + let alpha_powers = verifier.generate_challenge(max_degree as usize); + + // Step 3: Malicious Prover computes and sends fake u and v + let (fake_u, fake_v) = prover.compute_malicious_values(&alpha_powers); + + // Step 4: Verifier checks whether u = v^t (which will pass for the fake values) + verifier.verify(&fake_u, &fake_v) +} +``` + +## \\(+\\) Knowledge of Exponent Assumption To address the vulnerability where the verifier \\(\mathcal{B}\\) cannot distinguish if \\(v (= g^h)\\) from the prover is a power of \\(\alpha_i = g^{(s^i)}\\), we can employ the Knowledge of Exponent Assumption. This approach involves the following steps: @@ -74,7 +335,7 @@ To address the vulnerability where the verifier \\(\mathcal{B}\\) cannot disting Based on this assumption, we can design an improved protocol: -*\\(+\\) Knowledge of Exponent Assumption* +**Protocol:** - *\\(\mathcal{B}\\) randomly selects \\(s\\) and \\(r\\) from field \\(\mathbb{F}\\).* - *\\(\mathcal{B}\\) computes and sends \\(\{\alpha_1, \alpha_2, ..., \alpha_{n}\}\\) and \\(\{\alpha'\_1, \alpha'\_2, ..., \alpha'\_{n}\}\\), where \\(\alpha_i = g^{(s^i)}\\) and \\(\alpha' = \alpha_{r} = g^{(s^{i})r}\\).* @@ -82,14 +343,103 @@ Based on this assumption, we can design an improved protocol: - *\\(\mathcal{B}\\) checks whether \\(u^{r} = w\\).* - *\\(\mathcal{B}\\) checks whether \\(u = v^{t}\\).* - The prover can compute \\(g^{p'} = g^{P(sr)} = \alpha'^{c_1} (\alpha'^{2})^{c_2} \cdots (\alpha'^{n})^{c_n}\\) using powers of \\(\alpha'\\). This protocol now satisfies the properties of a SNARK: completeness, soundness, and efficiency. -### \\(+\\) Zero Knowledge +**Implementation:** + +```rust +pub struct Prover { + pub p: Polynomial, + pub t: Polynomial, + pub h: Polynomial, +} + +pub struct Verifier { + t: Polynomial, + s: F, + r: F, + g: F, +} + +impl Prover { + pub fn new(p: Polynomial, t: Polynomial) -> Self { + let h = p.clone() / t.clone(); + Prover { p, t, h } + } + + pub fn compute_values(&self, alpha_powers: &[F], alpha_prime_powers: &[F]) -> (F, F, F) { + let g_p = self.p.eval_with_powers(alpha_powers); + let g_h = self.h.eval_with_powers(alpha_powers); + let g_p_prime = self.p.eval_with_powers(alpha_prime_powers); + (g_p, g_h, g_p_prime) + } +} + +impl Verifier { + pub fn new(t: Polynomial, generator: i128) -> Self { + let mut rng = rand::thread_rng(); + let s = F::from_value(rng.gen_bigint_range( + &BigInt::zero(), + &BigInt::from(std::u64::MAX), + )); + let r = F::from_value(rng.gen_bigint_range( + &BigInt::zero(), + &BigInt::from(std::u64::MAX), + )); + let g = F::from_value(generator); + Verifier { t, s, r, g } + } + + pub fn generate_challenge(&self, max_degree: usize) -> (Vec, Vec) { + let mut alpha_powers = vec![]; + let mut alpha_prime_powers = vec![]; + + for i in 0..(max_degree + 1) { + alpha_powers.push( + self.g + .pow(self.s.clone().pow(i.to_bigint().unwrap()).get_value()), + ); + alpha_prime_powers.push(alpha_powers.last().unwrap().pow(self.r.get_value())); + } + + (alpha_powers, alpha_prime_powers) + } + + pub fn verify(&self, u: &F, v: &F, w: &F) -> bool { + let t_s = self.t.eval(&self.s); + let u_r = u.pow(self.r.clone().get_value()); + + // Check 1: u^r = w + let check1 = u_r == *w; + + // Check 2: u = v^t + let check2 = *u == v.pow(t_s.get_value()); + + check1 && check2 + } +} + +pub fn knowledge_of_exponent_protocol( + prover: &Prover, + verifier: &Verifier, +) -> bool { + // Step 1 & 2: Verifier generates a challenge + let max_degree = std::cmp::max(prover.p.degree(), prover.h.degree()) as usize; + let (alpha_powers, alpha_prime_powers) = verifier.generate_challenge(max_degree + 1); + + // Step 3: Prover computes and sends u = g^p, v = g^h, and w = g^p' + let (u, v, w) = prover.compute_values(&alpha_powers, &alpha_prime_powers); + + // Step 4 & 5: Verifier checks whether u^r = w and u = v^t + verifier.verify(&u, &v, &w) +} +``` + +## \\(+\\) Zero Knowledge To transform the above protocol into a zk-SNARK, we need to ensure that the verifier cannot learn anything about \\(P(x)\\) from the prover's information. This is achieved by having the prover obfuscate all information with a random secret value \\(\delta\\): -*\\(+\\) Zero Knowledge* +**Protocol:** - *\\(\mathcal{B}\\) randomly selects \\(s\\) and \\(r\\) from field \\(\mathbb{F}\\).* - *\\(\mathcal{B}\\) computes and sends \\(\\{\alpha_1, \alpha_2, ..., \alpha_{n}\\}\\) and \\(\\{\alpha\_1', \alpha'\_2, ..., \alpha'\_{n}\\}\\), where \\(\alpha_i = g^{(s^{i})}\\) and \\(\alpha_i' = \alpha_i^{r} = g^{(s^{i})r}\\).* @@ -98,15 +448,112 @@ To transform the above protocol into a zk-SNARK, we need to ensure that the veri - *\\(\mathcal{B}\\) checks whether \\(u'^{r} = w'\\).* - *\\(\mathcal{B}\\) checks whether \\(u' = v'^{t}\\).* - - By introducing the random value \\(\delta\\), the verifier can no longer learn anything about \\(p\\), \\(h\\), or \\(w\\), thus achieving zero knowledge. -### \\(+\\) Non-interactivity + +**Implementation:** + +```rust +pub struct Prover { + pub p: Polynomial, + pub t: Polynomial, + pub h: Polynomial, +} + +pub struct Verifier { + t: Polynomial, + s: F, + r: F, + g: F, +} + +impl Prover { + pub fn new(p: Polynomial, t: Polynomial) -> Self { + let h = p.clone() / t.clone(); + Prover { p, t, h } + } + + pub fn compute_values(&self, alpha_powers: &[F], alpha_prime_powers: &[F]) -> (F, F, F) { + let mut rng = rand::thread_rng(); + let delta = F::from_value(rng.gen_bigint_range( + &BigInt::zero(), + &BigInt::from(std::u64::MAX), + )); + + let g_p = self.p.eval_with_powers(alpha_powers); + let g_h = self.h.eval_with_powers(alpha_powers); + let g_p_prime = self.p.eval_with_powers(alpha_prime_powers); + + let u_prime = g_p.pow(delta.get_value()); + let v_prime = g_h.pow(delta.get_value()); + let w_prime = g_p_prime.pow(delta.get_value()); + + (u_prime, v_prime, w_prime) + } +} + +impl Verifier { + pub fn new(t: Polynomial, generator: i128) -> Self { + let mut rng = rand::thread_rng(); + let s = F::from_value(rng.gen_bigint_range( + &BigInt::zero(), + &BigInt::from(std::u64::MAX), + )); + let r = F::from_value(rng.gen_bigint_range( + &BigInt::zero(), + &BigInt::from(std::u64::MAX), + )); + let g = F::from_value(generator); + Verifier { t, s, r, g } + } + + pub fn generate_challenge(&self, max_degree: usize) -> (Vec, Vec) { + let mut alpha_powers = vec![]; + let mut alpha_prime_powers = vec![]; + + for i in 0..(max_degree + 1) { + alpha_powers.push( + self.g + .pow(self.s.clone().pow(i.to_bigint().unwrap()).get_value()), + ); + alpha_prime_powers.push(alpha_powers.last().unwrap().pow(self.r.get_value())); + } + + (alpha_powers, alpha_prime_powers) + } + + pub fn verify(&self, u_prime: &F, v_prime: &F, w_prime: &F) -> bool { + let t_s = self.t.eval(&self.s); + let u_prime_r = u_prime.pow(self.r.clone().get_value()); + + // Check 1: u'^r = w' + let check1 = u_prime_r == *w_prime; + + // Check 2: u' = v'^t + let check2 = *u_prime == v_prime.pow(t_s.get_value()); + + check1 && check2 + } +} + +pub fn zk_protocol(prover: &Prover, verifier: &Verifier) -> bool { + // Step 1 & 2: Verifier generates a challenge + let max_degree = std::cmp::max(prover.p.degree(), prover.h.degree()) as usize; + let (alpha_powers, alpha_prime_powers) = verifier.generate_challenge(max_degree + 1); + + // Step 3 & 4: Prover computes and sends u' = (g^p)^δ, v' = (g^h)^δ, and w' = (g^p')^δ + let (u_prime, v_prime, w_prime) = prover.compute_values(&alpha_powers, &alpha_prime_powers); + + // Step 5 & 6: Verifier checks whether u'^r = w' and u' = v'^t + verifier.verify(&u_prime, &v_prime, &w_prime) +} +``` + +## \\(+\\) Non-interactivity The previously described protocol requires each verifier to generate unique random values, which becomes inefficient when a prover needs to demonstrate knowledge to multiple verifiers. To address this, we aim to eliminate the interaction between the prover and verifier. One effective solution is the use of a trusted setup. -*\\(+\\) Non-Interactive: Trusted Setup* +**Protocol (Trusted Setup):** - ***Secret Seed:** A trusted third party generates the random values \\(s\\) and \\(r\\)* - ***Proof Key:** Provided to the prover* @@ -120,21 +567,133 @@ The previously described protocol requires each verifier to generate unique rand Then, the non-interactive protocol consists of two main parts: proof generation and verification. -*\\(+\\) Non-Interactive: Proof* +**Protocol (Proof):** - *\\(\mathcal{A}\\) receives the proof key* - *\\(\mathcal{A}\\) randomly selects \\(\delta\\) from field \\(\mathbb{F}\\).* - *\\(\mathcal{A}\\) broadcast the proof \\(\pi = (u' = (g^{p})^{\delta}, v' = (g^{h})^{\delta}, w' = (g^{p'})^{\delta})\\)* - Since \\(r\\) is not shared and already destroyed, the verifier \\(\mathcal{B}\\) cannot calculate \\(u'^{r}\\) to check \\(u'^{r} = w'\\). Instead, the verifier can use a paring with bilinear mapping; \\(u'^{r} = w'\\) is equivalent to \\(e(u' = (g^{p})^{\delta}, g^{r}) = e(w'=(g^{p'})^{\delta}, g)\\). -*\\(+\\) Non-Interactive: Verification* +**Protocol (Verification):** - *\\(\mathcal{B}\\) receives the verification key.* - *\\(\mathcal{B}\\) receives the proof \\(\pi\\).* - *\\(\mathcal{B}\\) checks whether \\(e(u', g^{r}) = e(w', g)\\).* - *\\(\mathcal{B}\\) checks whether \\(u' = v'^{t}\\).* + +**Implementation:** + +```rust +pub struct ProofKey { + alpha: Vec, + alpha_prime: Vec, +} + +pub struct VerificationKey { + g_r: G2Point, + g_t_s: G2Point, +} + +pub struct Proof { + u_prime: G1Point, + v_prime: G1Point, + w_prime: G1Point, +} + +pub struct TrustedSetup { + proof_key: ProofKey, + verification_key: VerificationKey, +} + +impl TrustedSetup { + pub fn generate(g1: &G1Point, g2: &G2Point, t: &Polynomial, n: usize) -> Self { + let mut rng = rand::thread_rng(); + let s = Fq::from_value(rng.gen_bigint_range(&BigInt::zero(), &BigInt::from(std::u32::MAX))); + let r = Fq::from_value(rng.gen_bigint_range(&BigInt::zero(), &BigInt::from(std::u32::MAX))); + + let mut alpha = Vec::with_capacity(n); + let mut alpha_prime = Vec::with_capacity(n); + + let mut s_power = Fq::one(); + for _ in 0..1 + n { + alpha.push(g1.clone() * s_power.clone().get_value()); + alpha_prime.push(g1.clone() * (s_power.clone() * r.clone()).get_value()); + s_power = s_power * s.clone(); + } + + let g_r = g2.clone() * r.clone().get_value(); + let g_t_s = g2.clone() * t.eval(&s).get_value(); + + TrustedSetup { + proof_key: ProofKey { alpha, alpha_prime }, + verification_key: VerificationKey { g_r, g_t_s }, + } + } +} + +pub struct Prover { + pub p: Polynomial, + pub h: Polynomial, +} + +impl Prover { + pub fn new(p: Polynomial, t: Polynomial) -> Self { + let h = p.clone() / t; + Prover { p, h } + } + + pub fn generate_proof(&self, proof_key: &ProofKey) -> Proof { + let mut rng = rand::thread_rng(); + let delta = + Fq::from_value(rng.gen_bigint_range(&BigInt::zero(), &BigInt::from(std::u32::MAX))); + + let g_p = self.p.eval_with_powers_on_curve(&proof_key.alpha); + let g_h = self.h.eval_with_powers_on_curve(&proof_key.alpha); + let g_p_prime = self.p.eval_with_powers_on_curve(&proof_key.alpha_prime); + + Proof { + u_prime: g_p * delta.get_value(), + v_prime: g_h * delta.get_value(), + w_prime: g_p_prime * delta.get_value(), + } + } +} + +pub struct Verifier { + pub g1: G1Point, + pub g2: G2Point, +} + +impl Verifier { + pub fn new(g1: G1Point, g2: G2Point) -> Self { + Verifier { g1, g2 } + } + + pub fn verify(&self, proof: &Proof, vk: &VerificationKey) -> bool { + // Check e(u', g^r) = e(w', g) + let pairing1 = optimal_ate_pairing(&proof.u_prime, &vk.g_r); + let pairing2 = optimal_ate_pairing(&proof.w_prime, &self.g2); + let check1 = pairing1 == pairing2; + + // Check e(u', g^t) = e(v', g) + let pairing3 = optimal_ate_pairing(&proof.u_prime, &self.g2); + let pairing4 = optimal_ate_pairing(&proof.v_prime, &vk.g_t_s); + let check2 = pairing3 == pairing4; + + check1 && check2 + } +} + +pub fn non_interactive_zkp_protocol( + prover: &Prover, + verifier: &Verifier, + setup: &TrustedSetup, +) -> bool { + let proof = prover.generate_proof(&setup.proof_key); + verifier.verify(&proof, &setup.verification_key) +} +``` diff --git a/docs/404.html b/docs/404.html index c9e7da8..f7ba931 100644 --- a/docs/404.html +++ b/docs/404.html @@ -3,7 +3,7 @@ - Page not found - MyZKP: Building Zero Knowledge Proof from Scratch + Page not found - Book of MyZKP @@ -92,7 +92,7 @@