Skip to content

Commit

Permalink
Merge branch 'main' into feat/discrete-fourier-transform
Browse files Browse the repository at this point in the history
  • Loading branch information
Autoparallel committed May 9, 2024
2 parents 4f22cf7 + eea6702 commit 494eeb7
Show file tree
Hide file tree
Showing 12 changed files with 961 additions and 78 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
cgt* # ignore instatiations of my test template

# don't leak secret env vars
.env
.env

# exclude compiled files and binaries
debug/
Expand All @@ -25,3 +25,5 @@ dump
### VIM ###
*.swp
*.swo

*.sage.py
81 changes: 80 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,89 @@
<!-- [![Documentation](https://docs.rs/ronkathon/badge.svg)](https://docs.rs/ronkathon) -->
</div>

## Overview
Ronkathon is an implementation of plonkish KZG-based proofs. It is inspired by the common python plonkathon repository, and plonk-by-hand. We use the same curve and field as plonk-by-hand (not secure), and are working towards building everything from scratch to understand everything from first principles.

## Resources

We have found the following resources helpful in understanding the foundational mathematics behind this implementation. After going through these, you should be able to understand the codebase. In order it is recommended to understand:
- Finite Fields
- Extension Fields
- Elliptic Curves over Fields and Extension Fields
- Embedding degree
- Polynomials
- FFTs
- Pairings

### Some notes on the KZG proof construction:
Lets start simple with a finite field and work up to creating two elliptic curve *groups* that have a pairing or bilinear map (more on that later).
First lets pick a finite field of prime order $p$, we pick $p=101$ since it is small and we are able to follow along plonk-by-hand.
In general large primes are good but we will use a small one just for the sake of example.
Next lets pick an elliptic curve $y^2=x^3+3$, there are some heuristics to curves that i encourage you to learn more about if you like but you can also black box and know that this is a good curve.
So now we have two algebraic structures:
- finite field $F_{101}$
- curve $y^2=x^3+3$
Initially this elliptic curve is just a continuous squiggle, which isn't the most useful. But we can make it discrete by constraining it's points to reside in the field.
Now it doesn't look like the squiggle we know and love but instead a lattice (you can see [here](https://andrea.corbellini.name/ecc/interactive/modk-add.html) by switching from real numbers to finite fields ).

Now we have a set of discrete points on the curve over the finite field that form a *[group](https://en.wikipedia.org/wiki/Group_(mathematics))*, a group has a single operation called the group operation, it is perhaps more abstract than a field.
The group operation on this set of curve points is point addition which we all know and love with the squiggly lines, intersections and reflections. From this group operation we can create point doubling, and as a result, scalar multiplication (how many times do we double a point) as handy abstractions over the single group operation.

To review we have a curve group call it $E1$ and the base field $F_{101}$
Elements in the curve group are points (pairs of integers) that lie in the field $F_{101}$.

Now to create a pairing friendly curve we first must find the curve groups order.
The order is how many times do we double the generator to get the generator again, the reason we can do this is because our group is cyclic.
Now if our base field $F_{101}$ is of prime order, then any point in the curve group is a generator.
So in practice you can pick a point and double it untill you get back to itself (remember to check the inverse!).
This defines the scalar field $F_r$ where $r$ is the order.
In our case this is $17$.
Once we have have this we can computer the embedding degree.
The embedding degree is the smallest number $k$ such that $r | p^k - 1$ where $r$ is the order of the curve: $17$
For us this is $2$, we can check that 17 divides $101^2 -1$ as $10200 / 17 = 600$ ✅.
So now we have an embedding degree of our curve.

The next step is to construct a field extension from the first field such that $f_{p^2}$ is a field extension of $f_p$, we extend with $x^2 + 2$ which is irreducible in $F_{101}$
The elements of the extension field are two degree polynomials where the coefficients are in $F_{101}$
Now we can construct pairing friendly curve over the field extension and get a generator point for the second curve: $g2 = (31, 36x)$
Our second curve group now E2, is over the same curve but now over the field extension.
It's points are now represented by two degree polynomials (because our embedding degree is two), not integers.
We now have two pairing friendly groups $E1$ and $E2$ and generators for both of them.

The next step is to construct the structured refrence string SRS with g1 and g2. The structured refrence string is generated by multiplying the generator points by some randomness $\{S^i\}$, the SRS needs to be a vector of length $t$ where $t$ is the number of constraints in the proof.
This is same as the degree of the polynomial which we would like to prove knowledge of.
KZG Proves an arbitrary polynomial. Plonk can be used to represent some computation as a polynomial.

TODO: Talk more about plonk.

Commit to a polynomial using the g1_SRS: This is done by multiplying the polynomial coefficients by the g1_SRS points (scalar multiplication in the curve group) and adding the resulting points to each other to get a single point that represents the commitment call it `p_commit`.

Opening involves choosing a point to evauluate the polynomial at and dividing the polynomial by .... (need the notes for this). the resulting polynomial is also combined with the g1_SRS to get a new commitment curve point call it `q_commit`.

Then we do the pairing check.

$e(q_{commit}, g2srs[0] - g2* point) = e(p_{commit} - g1srs[0] * val, g2)$



### Theoretic Resources
- [A gentle introduction to Fast Fourier Transforms over Finite Fields](https://vitalik.eth.limo/general/2019/05/12/fft.html)
- [Introduction to Pairings](https://vitalik.eth.limo/general/2017/01/14/exploring_ecp.html)
- [KZG introduction by dankrad](https://dankradfeist.de/ethereum/2020/06/16/kate-polynomial-commitments.html)
- [Pairings in depth](https://static1.squarespace.com/static/5fdbb09f31d71c1227082339/t/5ff394720493bd28278889c6/1609798774687/PairingsForBeginners.pdf)
- [Plonk by Hand P1](https://research.metastate.dev/plonk-by-hand-part-1/)
- [Plonk by Hand P2](https://research.metastate.dev/plonk-by-hand-part-2-the-proof/)
### Code Refrences
- [Plonkathon](https://github.com/0xPARC/plonkathon/blob/main/README.md)
- [Plonky3](https://github.com/Plonky3/Plonky3)
- [py_pairing](https://github.com/ethereum/py_pairing/tree/master)
- [arkworks](https://github.com/arkworks-rs)


## Math
To see computations used in the background, go to the `math/` directory.
From there, you can run the `.sage` files in a SageMath environment.
In particular, the `math/field.sage` computes roots of unity in the `PlutoField` which is of size 101.
In particular, the `math/field.sage` computes roots of unity in the `PlutoField` which is of size 101. To install sage on your machine, follow the instructions [here](https://doc.sagemath.org/html/en/installation/index.html). If you are on a Mac, you can install it via homebrew with `brew install --cask sage`.

## License
Licensed under your option of either:
Expand Down
36 changes: 31 additions & 5 deletions math/field.sage
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ print("The quotient is: ", quotient)

# Find a primitive root of unity using the formula:
# omega = primitive_element^quotient
omega_m = primitive_element^quotient
omega_m = primitive_element ^ quotient
print("The ", m, "th root of unity is: ", omega_m)

# Check that this is actually a root of unity:
assert omega_m^m == 1
print(omega_m, "^", m, " = ", omega_m^m)
assert omega_m ^ m == 1
print(omega_m, "^", m, " = ", omega_m ^ m)
######################################################################

######################################################################
Expand All @@ -35,8 +35,8 @@ print("The quotient is: ", quotient)

# Find a primitive root of unity using the formula:
# omega = primitive_element^quotient
omega_n = primitive_element^quotient
print("The ", n, "th root of unity is: ", omega_n)
omega_n = primitive_element ^ quotient
print("The", n, "th root of unity is: ", omega_n)

# Check that this is actually a root of unity:
assert omega_n^n == 1
Expand All @@ -59,4 +59,30 @@ print("The ", l, "th root of unity is: ", omega_l)
# Check that this is actually a root of unity:
assert omega_l^l == 1
print(omega_l, "^", l, " = ", omega_l^l)
assert omega_n ^ n == 1
print(omega_n, "^", n, " = ", omega_n ^ n)
######################################################################

# extension field of degree 2
Ft.<t> = F[]

# irreducible element: t^2-2
P = Ft(t ^ 2 - 2)
assert P.is_irreducible()

F_2 = GF(101 ^ 2, name="t", modulus=P)
print("extension field:", F_2, "of order:", F_2.order())

# Primitive element
f_2_primitive_element = F_2([2, 1])
print("Primitive element of F_2:", f_2_primitive_element, f_2_primitive_element.multiplicative_order())

# 100th root of unity
F_2_order = F_2.order()
root_of_unity_order = 100
quotient = (F_2_order-1)//root_of_unity_order

f_2_omega_n = f_2_primitive_element ^ quotient
print("The", root_of_unity_order, "th root of unity of extension field is: ", f_2_omega_n)

######################################################################
117 changes: 117 additions & 0 deletions src/curves/g1_curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::ops::Add;

use super::CurveParams;
use crate::field::{gf_101::GF101, FiniteField};

/// The Elliptic curve $y^2=x^3+3$, i.e.
/// - a = 0
/// - b = 3
/// - generator todo
/// - order todo
/// - field element type todo, but mock with u64 - bad thor, u64 does not implement p3_field
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct C101;

impl CurveParams for C101 {
type FieldElement = GF101;

const EQUATION_A: Self::FieldElement = GF101::new(0);
const EQUATION_B: Self::FieldElement = GF101::new(3);
const GENERATOR: (Self::FieldElement, Self::FieldElement) = (GF101::new(1), Self::TWO);
const ORDER: u32 = GF101::ORDER;
const THREE: Self::FieldElement = GF101::new(3);
const TWO: Self::FieldElement = GF101::new(2);
}

mod test {
use super::*;
use crate::curves::AffinePoint;
type F = GF101;

#[test]
fn point_doubling() {
let g = AffinePoint::<C101>::generator();

let two_g = g.point_doubling();
let expected_2g = AffinePoint::<C101>::new(F::new(68), F::new(74));
let expected_negative_2g = AffinePoint::<C101>::new(F::new(68), F::new(27));
assert_eq!(two_g, expected_2g);
assert_eq!(-two_g, expected_negative_2g);

let four_g = two_g.point_doubling();
let expected_4g = AffinePoint::<C101>::new(F::new(65), F::new(98));
let expected_negative_4g = AffinePoint::<C101>::new(F::new(65), F::new(3));
assert_eq!(four_g, expected_4g);
assert_eq!(-four_g, expected_negative_4g);

let eight_g = four_g.point_doubling();
let expected_8g = AffinePoint::<C101>::new(F::new(18), F::new(49));
let expected_negative_8g = AffinePoint::<C101>::new(F::new(18), F::new(52));
assert_eq!(eight_g, expected_8g);
assert_eq!(-eight_g, expected_negative_8g);

let sixteen_g = eight_g.point_doubling();
let expected_16g = AffinePoint::<C101>::new(F::new(1), F::new(99));
let expected_negative_16g = AffinePoint::<C101>::new(F::new(1), F::new(2));
assert_eq!(sixteen_g, expected_16g);
assert_eq!(-sixteen_g, expected_negative_16g);
assert_eq!(g, -sixteen_g);
}

#[test]
fn order_17() {
let g = AffinePoint::<C101>::generator();
let mut g_double = g.point_doubling();
let mut count = 2;
while g_double != g && -g_double != g {
g_double = g_double.point_doubling();
count *= 2;
}
assert_eq!(count + 1, 17);
}

#[test]
fn point_addition() {
let g = AffinePoint::<C101>::generator();
let two_g = g.point_doubling();
let three_g = g + two_g;
let expected_3g = AffinePoint::<C101>::new(F::new(26), F::new(45));
let expected_negative_3g = AffinePoint::<C101>::new(F::new(26), F::new(56));
assert_eq!(three_g, expected_3g);
assert_eq!(-three_g, expected_negative_3g);

let four_g = g + three_g;
let expected_4g = AffinePoint::<C101>::new(F::new(65), F::new(98));
let expected_negative_4g = AffinePoint::<C101>::new(F::new(65), F::new(3));
assert_eq!(four_g, expected_4g);
assert_eq!(-four_g, expected_negative_4g);

let five_g = g + four_g;
let expected_5g = AffinePoint::<C101>::new(F::new(12), F::new(32));
let expected_negative_5g = AffinePoint::<C101>::new(F::new(12), F::new(69));
assert_eq!(five_g, expected_5g);
assert_eq!(-five_g, expected_negative_5g);

assert_eq!(g + AffinePoint::new_infty(), g);
assert_eq!(AffinePoint::new_infty() + g, g);
assert_eq!(g + (-g), AffinePoint::new_infty());
}

#[test]
fn scalar_multiplication_rhs() {
let g = AffinePoint::<C101>::generator();
let two_g = g * 2;
let expected_2g = g.point_doubling();
assert_eq!(two_g, expected_2g);
assert_eq!(-two_g, -expected_2g);
}

#[test]
fn scalar_multiplication_lhs() {
let g = AffinePoint::<C101>::generator();
let two_g = 2 * g;
let expected_2g = g.point_doubling();
assert_eq!(two_g, expected_2g);
assert_eq!(-two_g, -expected_2g);
}
}
83 changes: 83 additions & 0 deletions src/curves/g2_curve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::ops::Add;

use super::CurveParams;
use crate::field::{gf_101::GF101, gf_101_2::QuadraticPlutoField, ExtensionField, FiniteField};

#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub struct G2Curve {}
// The Elliptic curve $y^2=x^3+3$, i.e.
// - a = 0
// - b = 3

impl CurveParams for G2Curve {
type FieldElement = QuadraticPlutoField<GF101>;

const EQUATION_A: Self::FieldElement = QuadraticPlutoField::<GF101>::ZERO;
const EQUATION_B: Self::FieldElement =
QuadraticPlutoField::<GF101>::new(GF101::new(3), GF101::ZERO);
const GENERATOR: (Self::FieldElement, Self::FieldElement) = (
QuadraticPlutoField::<GF101>::new(GF101::new(36), GF101::ZERO),
QuadraticPlutoField::<GF101>::new(GF101::ZERO, GF101::new(31)),
);
const ORDER: u32 = 289;
// extension field subgroup should have order r^2 where r is order of first group
const THREE: QuadraticPlutoField<GF101> =
QuadraticPlutoField::<GF101>::new(GF101::new(3), GF101::ZERO);
const TWO: QuadraticPlutoField<GF101> =
QuadraticPlutoField::<GF101>::new(GF101::TWO, GF101::ZERO);
}

// a naive impl with affine point

impl G2Curve {
pub fn on_curve(
x: QuadraticPlutoField<GF101>,
y: QuadraticPlutoField<GF101>,
) -> (QuadraticPlutoField<GF101>, QuadraticPlutoField<GF101>) {
println!("X: {:?}, Y: {:?}", x, y);
// TODO Continue working on this
// ( x ) ( y ) ( x , y )
// example: plug in ((36, 0), (0, 31)): (36, 31t)
// x = 36, y = 31t,
// curve : y^2=x^3+3,

// y = (31t)^2 = 52 * t^2
// check if there are any x terms, if not, element is in base field
let mut LHS = x;
let mut RHS = y;
if x.value[1] != GF101::ZERO {
LHS = x * x * (-GF101::new(2)) - Self::EQUATION_B;
} else {
LHS = x * x * x - Self::EQUATION_B;
}
if y.value[1] != GF101::ZERO {
// y has degree two so if there is a x -> there will be an x^2 term which we substitude with
// -2 since... TODO explain this and relationship to embedding degree
RHS *= -GF101::new(2);
}
// minus
LHS -= Self::EQUATION_B;
assert_eq!(LHS, RHS, "Point is not on curve");
(x, y)
}
}

mod test {
use super::*;
use crate::curves::AffinePoint;

// #[test]
// fn on_curve() {
// let gen = G2Curve::on_curve(G2Curve::GENERATOR.0, G2Curve::GENERATOR.1);
// }

// #[test]
// fn doubling() {
// let g = AffinePoint::<G2Curve>::generator();
// println!("g: {:?}", g)

// // want to asset that g = (36, 31*X)
// // right now this ^ fails to construct as it doesn't believe that the generator is a valid
// point on the curve // want to asset that 2g = (90 , 82*X)
// }
}
Loading

0 comments on commit 494eeb7

Please sign in to comment.