Merge ac7eeb75cdac0cfa5838d7729dda0e1ec9b2f9f6 into 7ba808630f2e1fa3231503b82429b6f88290b07b

This commit is contained in:
Manish Kumar 2025-03-05 01:45:04 +01:00 committed by GitHub
commit 4a94f1d169
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1463 additions and 4 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
build
target
a.out
Cargo.lock

6
.gitmodules vendored
View File

@ -13,3 +13,9 @@
[submodule "hash/snark/src/hash-circuits"]
path = hash/snark/external/hash-circuits
url = https://github.com/faulhornlabs/hash-circuits
[submodule "hash/risc0/external/risc0"]
path = hash/risc0/external/risc0
url = https://github.com/risc0/risc0.git
[submodule "hash/plonky2/external/zk_evm"]
path = hash/plonky2/external/zk_evm
url = https://github.com/hashcloak/zk_evm.git

@ -1 +1 @@
Subproject commit dd30dcb00221591db3a983e0215b81d86cff941d
Subproject commit 4d32708f511fd85c6b0fb131295cc73224246738

@ -1 +1 @@
Subproject commit 4dd0a02f1afd338f5207e40a47cac6705196b490
Subproject commit dbd2630daa6d599a9e78ad247e6858baf41664da

@ -1 +1 @@
Subproject commit 96e349786bd004e64a9cf50e0122f89863b24e92
Subproject commit 0fb198a9087531f32bb00bd13d4feaf813bc473a

View File

@ -0,0 +1,21 @@
[package]
name = "plonky2_hash_benchmarks"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# TODO: This can be later changed to original github
plonky2_u32 ={ git = "https://github.com/hashcloak/plonky2-u32.git"}
plonky2 = "0.2.2"
rand = "0.8.5"
anyhow = "1.0.86"
sha2 = "0.10"
tiny-keccak={version="2.0.2", features=["keccak"]}
hex="0.4.3"
evm_arithmetization ={ path = "../external/zk_evm/evm_arithmetization" }
starky = "0.4.0"
env_logger = "0.11.3"
log = "0.4.21"

View File

@ -0,0 +1,11 @@
Plonky2 Hash Circuit Benchmarking
--------------------------------
- The `plonky2/bench` contains the following hash circuit benchamrking code: sha256, keccak256 and poseidon.
- The code for sha256 is taken from [plonky2-sha256](https://github.com/polymerdao/plonky2-sha256).
- The keccak hash has two different implementation taken from [plonky2-keccak256](https://github.com/qope/plonky2-keccak256) and Polygon-zero [zk_evm](https://github.com/0xPolygonZero/zk_evm/tree/develop).
- The `build.sh` script builds the whole code. You need rust nightly version to build the code. The script overrides the default rust version to nightly.
- `run.sh` and `run_tree.sh` runs the benchmark. (`run.sh` for sha256, keccak, and keccak-polygon and `run_tree.sh` for poseidon)
- Benchmarks can be parameterized using environment variables. By convention, we start the names of these environment variables with the `ZKBENCH_` prefix.
- By default the `run.sh` will run the sha256 benchmark over 256 Bytes of data. other hashes can be run by settig the environment variables accordingly.
- By default the `run_tree.sh` will run the poseidon benchmark over tree depth 4.
- Additional files `bench.cfg` and `bench_tree.cfg` specifies the configurations and parameters.

View File

@ -0,0 +1,11 @@
name: "Plonky2 hashes circuit benchmarking"
author:
timeout: 200
params:
[ HASH_TYPE: [ "sha256", "keccak", "keccak-polygon"]
, INPUT_SIZE_BYTES: [ 256, 512, 1024, 2048 ]
]
tags: plonky2, $HASH_TYPE
comments:
The benchmarks includes for sha256, keccak and poseidon hash.

View File

@ -0,0 +1,10 @@
name: "Plonky2 hashes circuit benchmarking"
author:
timeout: 100
params:
[ HASH_TYPE_TREE: [ "poseidon"]
, TREE_DEPTH: [ 2, 4, 8, 16 ]
]
tags: plonky2, $HASH_TYPE_TREE
comments:
The benchmarks includes for poseidon hash.

9
hash/plonky2/bench/build.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# Set nightly as the default toolchain
rustup override set nightly
# Build
RUSTFLAGS=-Ctarget-cpu=native cargo build --release --bin plonky2_hash_benchmarks

15
hash/plonky2/bench/run.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
if [ -z ${ZKBENCH_HASH_TYPE} ]; then
ZKBENCH_HASH_TYPE="sha256"
fi
if [ -z ${ZKBENCH_INPUT_SIZE_BYTES} ]; then
ZKBENCH_INPUT_SIZE_BYTES=256
fi
echo "Running benchmarks with the following configurations:"
echo "HASH = $ZKBENCH_HASH_TYPE"
echo "Input Size (Bytes) = $ZKBENCH_INPUT_SIZE_BYTES"
# Run the benchmarks
./target/release/plonky2_hash_benchmarks $ZKBENCH_HASH_TYPE $ZKBENCH_INPUT_SIZE_BYTES

15
hash/plonky2/bench/run_tree.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
if [ -z ${ZKBENCH_HASH_TYPE_TREE} ]; then
ZKBENCH_HASH_TYPE_TREE="poseidon"
fi
if [ -z ${ZKBENCH_TREE_DEPTH} ]; then
ZKBENCH_TREE_DEPTH=4
fi
echo "Running benchmarks with the following configurations:"
echo "HASH = $ZKBENCH_HASH_TYPE_TREE"
echo "Tree Depth = $ZKBENCH_TREE_DEPTH"
# Run the benchmarks
./target/release/plonky2_hash_benchmarks $ZKBENCH_HASH_TYPE_TREE $ZKBENCH_TREE_DEPTH

View File

@ -0,0 +1,78 @@
use plonky2::hash::hash_types::RichField;
use plonky2::field::extension::Extendable;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::iop::target::BoolTarget;
pub fn xor_circuit<F, const D: usize>(
a: BoolTarget,
b: BoolTarget,
builder: &mut CircuitBuilder<F, D>,
) -> BoolTarget
where
F: RichField + Extendable<D>,
{
// xor(a, b) = a*(1-b) + (1-a)*b = a + b - 2*ab
let b_minus_2ab = builder.arithmetic(-F::TWO, F::ONE, a.target, b.target, b.target);
let a_plus_b_minus_2ab = builder.add(a.target, b_minus_2ab);
BoolTarget::new_unsafe(a_plus_b_minus_2ab)
}
pub fn xor_const_circuit<F, const D: usize>(
a: BoolTarget,
b: bool,
builder: &mut CircuitBuilder<F, D>,
) -> BoolTarget
where
F: RichField + Extendable<D>,
{
// b = 0 => xor(a, b) = a
// b = 1 => xor(a, b) = 1 - a = not(a)
if b {
builder.not(a)
} else {
a
}
}
// reffered to https://github.com/polymerdao/plonky2-sha256
/// 0 -> [0, 1, 2, ..., 63]
/// 1 -> [63, 0, 1, ..., 62]
/// 63 -> [1, 2, ..., 63, 0]
pub fn rotate_u64(y: usize) -> Vec<usize> {
let mut res = Vec::new();
for i in 64 - y..64 {
res.push(i);
}
for i in 0..64 - y {
res.push(i);
}
res
}
#[allow(dead_code)]
pub fn from_bits_to_u64(bools: &[bool]) -> u64 {
let mut result: u64 = 0;
let mut shift = 0;
for &bit in bools {
if bit {
result |= 1 << shift;
}
shift += 1;
if shift == 64 {
break;
}
}
result
}
pub fn u64_to_bits(num: u64) -> Vec<bool> {
let mut result = Vec::with_capacity(64);
let mut n = num;
for _ in 0..64 {
result.push(n & 1 == 1);
n >>= 1;
}
result
}

View File

@ -0,0 +1,61 @@
use plonky2::iop::target::BoolTarget;
use plonky2::hash::hash_types::RichField;
use plonky2::field::extension::Extendable;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2_u32::gadgets::arithmetic_u32::U32Target;
use plonky2_u32::gadgets::arithmetic_u32::CircuitBuilderU32;
pub fn add_u32<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
b: &U32Target,
) -> U32Target {
let (res, _carry) = builder.add_u32(*a, *b);
res
}
pub fn u32_to_bits_target<F: RichField + Extendable<D>, const D: usize, const B: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
) -> Vec<BoolTarget> {
let mut res = Vec::new();
let bit_targets = builder.split_le_base::<B>(a.0, 32);
for i in (0..32).rev() {
res.push(BoolTarget::new_unsafe(bit_targets[i]));
}
res
}
pub fn bits_to_u32_target<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
bits_target: Vec<BoolTarget>,
) -> U32Target {
let bit_len = bits_target.len();
assert_eq!(bit_len, 32);
U32Target(builder.le_sum(bits_target[0..32].iter().rev()))
}
// x>>y
// Assume: 0 at index 32
pub fn shift32(y: usize) -> Vec<usize> {
let mut res = Vec::new();
for _ in 32 - y..32 {
res.push(32);
}
for i in 0..32 - y {
res.push(i);
}
res
}
// define ROTATE(x, y) (((x)>>(y)) | ((x)<<(32-(y))))
pub fn rotate32(y: usize) -> Vec<usize> {
let mut res = Vec::new();
for i in 32 - y..32 {
res.push(i);
}
for i in 0..32 - y {
res.push(i);
}
res
}

View File

@ -0,0 +1,152 @@
use plonky2::hash::hash_types::RichField;
use plonky2::field::extension::Extendable;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use std::marker::PhantomData;
use plonky2::iop::target::BoolTarget;
use plonky2::iop::witness::PartialWitness;
use plonky2::iop::witness::WitnessWrite;
use crate::arithmetic::binary_arithmetic::{rotate_u64,xor_const_circuit,u64_to_bits,xor_circuit};
#[derive(Clone, Debug)]
pub struct U64Target<F, const D: usize> {
pub bits: Vec<BoolTarget>,
_phantom: PhantomData<F>,
}
impl<F, const D: usize> U64Target<F, D>
where
F: RichField + Extendable<D>,
{
pub fn new(builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
for _ in 0..64 {
result.push(builder.add_virtual_bool_target_safe());
}
Self {
bits: result,
_phantom: PhantomData,
}
}
pub fn from(bits: Vec<BoolTarget>) -> Self {
assert_eq!(bits.len(), 64);
Self {
bits,
_phantom: PhantomData,
}
}
pub fn set_witness(&self, bits: Vec<bool>, pw: &mut PartialWitness<F>) {
for i in 0..64 {
pw.set_bool_target(self.bits[i], bits[i]);
}
}
pub fn constant(x: u64, builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
let x_bits = u64_to_bits(x);
for i in 0..64 {
result.push(builder.constant_bool(x_bits[i]));
}
Self {
bits: result,
_phantom: PhantomData,
}
}
pub fn connect(&self, other: &Self, builder: &mut CircuitBuilder<F, D>) {
for i in 0..64 {
builder.connect(self.bits[i].target, other.bits[i].target);
}
}
pub fn to_bits(&self, builder: &mut CircuitBuilder<F, D>) -> Vec<BoolTarget> {
let output = Self::new(builder);
self.connect(&output, builder);
output.bits
}
pub fn xor(&self, other: &Self, builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
for i in 0..64 {
let xor_target = xor_circuit(self.bits[i], other.bits[i], builder);
result.push(xor_target);
}
Self {
bits: result,
_phantom: PhantomData,
}
}
pub fn xor_const(&self, other: u64, builder: &mut CircuitBuilder<F, D>) -> Self {
let other_bits = u64_to_bits(other);
let mut result = vec![];
for i in 0..64 {
let xor_target = xor_const_circuit(self.bits[i], other_bits[i], builder);
result.push(xor_target);
}
Self {
bits: result,
_phantom: PhantomData,
}
}
/* Rotate left by n
* Note that the input parameter n is constant. It is not necessary to make n a constant target or public input,
* because different n generates a different circuit.
*/
pub fn rotl(&self, n: usize) -> Self {
let rotate = rotate_u64(n);
let mut output = vec![];
for i in 0..64 {
output.push(self.bits[rotate[i]]);
}
Self {
bits: output,
_phantom: PhantomData,
}
}
pub fn and(&self, other: &Self, builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
for i in 0..64 {
result.push(builder.and(self.bits[i], other.bits[i]));
}
Self {
bits: result,
_phantom: PhantomData,
}
}
pub fn not(&self, builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
for i in 0..64 {
result.push(builder.not(self.bits[i]));
}
Self {
bits: result,
_phantom: PhantomData,
}
}
/// Calculate `self & !other`.
pub fn and_not(&self, other: &Self, builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
for i in 0..64 {
// x(1 - y) = x - xy
result.push(BoolTarget::new_unsafe(builder.arithmetic(
F::NEG_ONE,
F::ONE,
self.bits[i].target,
other.bits[i].target,
self.bits[i].target,
)));
}
Self {
bits: result,
_phantom: PhantomData,
}
}
}

View File

@ -0,0 +1,312 @@
// code taken from https://github.com/qope/plonky2-keccak256/tree/main
use std::marker::PhantomData;
use plonky2::{
field::extension::Extendable,
hash::hash_types::RichField,
iop::{
target::BoolTarget,
witness::{PartialWitness, WitnessWrite},
},
plonk::{
circuit_builder::CircuitBuilder,
circuit_data::CircuitConfig,
config::PoseidonGoldilocksConfig,
},
field::goldilocks_field::GoldilocksField,
};
use std::time::Instant;
use rand::Rng;
use tiny_keccak::{Keccak,Hasher};
use anyhow::Result;
use crate::arithmetic::{
binary_arithmetic::xor_circuit,
u64_arithmetic::U64Target
};
pub const ROUND_CONSTANTS: [u64; 24] = [
1u64,
0x8082u64,
0x800000000000808au64,
0x8000000080008000u64,
0x808bu64,
0x80000001u64,
0x8000000080008081u64,
0x8000000000008009u64,
0x8au64,
0x88u64,
0x80008009u64,
0x8000000au64,
0x8000808bu64,
0x800000000000008bu64,
0x8000000000008089u64,
0x8000000000008003u64,
0x8000000000008002u64,
0x8000000000000080u64,
0x800au64,
0x800000008000000au64,
0x8000000080008081u64,
0x8000000000008080u64,
0x80000001u64,
0x8000000080008008u64,
];
pub const ROTR: [usize; 25] = [
0, 1, 62, 28, 27, 36, 44, 6, 55, 20, 3, 10, 43, 25, 39, 41, 45, 15, 21, 8, 18, 2, 61, 56, 14,
];
#[derive(Clone, Debug)]
pub struct KeccakTarget<F, const D: usize> {
words: Vec<U64Target<F, D>>,
_phantom: PhantomData<F>,
}
impl<F, const D: usize> KeccakTarget<F, D>
where
F: RichField + Extendable<D>,
{
pub fn new(builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = vec![];
for _ in 0..25 {
result.push(U64Target::new(builder));
}
Self {
words: result,
_phantom: PhantomData,
}
}
pub fn set_witness(&self, bits: Vec<bool>, pw: &mut PartialWitness<F>) {
assert_eq!(bits.len(), 1600);
for i in 0..25 {
self.words[i].set_witness(bits[i * 64..(i + 1) * 64].to_vec(), pw);
}
}
pub fn connect(&self, other: &Self, builder: &mut CircuitBuilder<F, D>) {
for i in 0..25 {
self.words[i].connect(&other.words[i], builder);
}
}
pub fn from(bits: Vec<BoolTarget>) -> Self {
let mut result = vec![];
for i in 0..25 {
result.push(U64Target::from(bits[i * 64..(i + 1) * 64].to_vec()));
}
Self {
words: result,
_phantom: PhantomData,
}
}
pub fn keccak_round(&mut self, rc: u64, builder: &mut CircuitBuilder<F, D>) {
// θ step
let mut c = vec![];
for x in 0..5 {
let xor01 = self.words[x].xor(&self.words[x + 5], builder);
let xor012 = xor01.xor(&self.words[x + 2 * 5], builder);
let xor0123 = xor012.xor(&self.words[x + 3 * 5], builder);
let xor01234 = xor0123.xor(&self.words[x + 4 * 5], builder);
c.push(xor01234);
}
let mut d = vec![];
for x in 0..5 {
let rot_c = c[(x + 1) % 5].rotl(1);
d.push(c[(x + 4) % 5].xor(&rot_c, builder));
}
for x in 0..5 {
for y in 0..5 {
self.words[x + y * 5] = self.words[x + y * 5].xor(&d[x], builder);
}
}
// ρ and π steps
let mut b_words: [Option<U64Target<F, D>>; 25] = [(); 25].map(|_| None);
for x in 0..5 {
for y in 0..5 {
let rot_self = self.words[x + y * 5].rotl(ROTR[x + y * 5]);
b_words[y + ((2 * x + 3 * y) % 5) * 5] = Some(rot_self);
}
}
let b = KeccakTarget {
words: b_words.into_iter().map(|x| x.unwrap()).collect(),
_phantom: PhantomData,
};
// χ step
for x in 0..5 {
for y in 0..5 {
// b.words[(x + 2) % 5 + y * 5] & !b.words[(x + 1) % 5 + y * 5]
let and_not_b =
b.words[(x + 2) % 5 + y * 5].and_not(&b.words[(x + 1) % 5 + y * 5], builder);
self.words[x + y * 5] = b.words[x + y * 5].xor(&and_not_b, builder);
}
}
self.words[0] = self.words[0].xor_const(rc, builder);
}
pub fn keccakf(&self, builder: &mut CircuitBuilder<F, D>) -> Self {
let mut result = self.clone();
for round_constant in ROUND_CONSTANTS.into_iter().take(24) {
result.keccak_round(round_constant, builder);
}
result
}
}
pub fn keccak256_circuit<F, const D: usize>(
input: Vec<BoolTarget>,
builder: &mut CircuitBuilder<F, D>,
) -> Vec<BoolTarget>
where
F: RichField + Extendable<D>,
{
assert_eq!(input.len() % 8, 0); // input should be bytes.
let block_size_in_bytes = 136; // in bytes
let input_len_in_bytes = input.len() / 8;
let num_blocks = input_len_in_bytes / block_size_in_bytes + 1;
let mut padded = vec![];
for _ in 0..block_size_in_bytes * 8 * num_blocks {
padded.push(builder.add_virtual_bool_target_safe());
}
// register input
for i in 0..input_len_in_bytes * 8 {
builder.connect(padded[i].target, input[i].target);
}
// append 0x01 = 1000 0000 after the last input
let true_target = builder.constant_bool(true);
builder.connect(padded[input_len_in_bytes * 8].target, true_target.target);
// pad 0s
let false_target = builder.constant_bool(false);
let last_index = padded.len() - 1;
for i in input_len_in_bytes * 8 + 1..last_index {
builder.connect(padded[i].target, false_target.target);
}
// xor 0x80 = 0000 0001 with the last byte.
// however the last bit is ensured to be 0, so just fill 1.
builder.connect(padded[last_index].target, true_target.target);
let mut m = KeccakTarget::new(builder);
for i in 0..1600 {
let word = i / 64;
let bit = i % 64;
builder.connect(m.words[word].bits[bit].target, false_target.target);
}
for i in 0..num_blocks {
for j in 0..block_size_in_bytes * 8 {
let word = j / 64;
let bit = j % 64;
let xor_t = xor_circuit(
m.words[word].bits[bit],
padded[i * block_size_in_bytes * 8 + j],
builder,
);
m.words[word].bits[bit] = xor_t;
}
m = m.keccakf(builder);
}
let mut z = Vec::new();
for i in 0..256 {
let new_target = builder.add_virtual_bool_target_safe();
let word = i / 64;
let bit = i % 64;
builder.connect(new_target.target, m.words[word].bits[bit].target);
z.push(new_target);
}
z
}
pub fn keccak_bench(size: usize) -> Result<()>{
type F = GoldilocksField;
type C = PoseidonGoldilocksConfig;
const D: usize = 2;
let input_bytes = generate_data(size);
let input = hex::encode(input_bytes);
let expected_output = expected_keccak(&hex::decode(input.clone()).unwrap());
let input_bits = hex_str_to_bits(input.as_str())?;
let exptected_output_bits = hex_str_to_bits(&expected_output)?;
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut input_t = vec![];
for i in 0..input_bits.len() {
input_t.push(builder.constant_bool(input_bits[i]));
}
let output_t = keccak256_circuit(input_t, &mut builder);
let mut pw = PartialWitness::new();
for i in 0..256 {
pw.set_bool_target(output_t[i], exptected_output_bits[i]);
}
let circuit_size = builder.num_gates();
let data = builder.build::<C>();
let (proof_generation_time, proof) = {
let now = Instant::now();
let proof = data.prove(pw)?;
(now.elapsed(), proof)
};
let proof_size = proof.to_bytes().len();
let (verification_time, verification_result) = {
let now = Instant::now();
let res = data.verify(proof);
(now.elapsed(), res)
};
eprintln!("circuit size: {}", circuit_size);
eprintln!("proof generation time: {:?}", proof_generation_time);
eprintln!("verification time: {:?}", verification_time);
eprintln!("proof size: {:?}", proof_size);
verification_result
}
fn generate_data(size: usize) -> Vec<u8> {
let mut rng = rand::thread_rng();
let mut bytes = vec![0u8; size];
rng.fill(&mut bytes[..]);
bytes
}
fn u8_to_bits(num: u8) -> Vec<bool> {
let mut result = Vec::with_capacity(8);
let mut n = num;
for _ in 0..8 {
result.push(n & 1 == 1);
n >>= 1;
}
result
}
fn hex_str_to_bits(input: &str) -> Result<Vec<bool>> {
let input_u8 = hex::decode(input)?;
let input_bits = input_u8
.iter()
.flat_map(|x| u8_to_bits(*x))
.collect::<Vec<_>>();
Ok(input_bits)
}
fn expected_keccak(input: &[u8]) -> String {
let mut hasher = Keccak::v256();
hasher.update(input);
let mut hash = [0u8; 32];
hasher.finalize(&mut hash);
hex::encode(hash)
}

View File

@ -0,0 +1,161 @@
// original source: https://github.com/0xPolygonZero/zk_evm/tree/develop
use evm_arithmetization::{
keccak::keccak_stark::KeccakStark,
prover::prove_single_table,
StarkConfig
};
use anyhow::Result;
use plonky2::{
fri::oracle::PolynomialBatch,
iop::challenger::Challenger,
plonk::config::{GenericConfig, PoseidonGoldilocksConfig},
field::polynomial::PolynomialValues,
field::types::Field,
timed,
util::timing::TimingTree,
};
use starky::{
cross_table_lookup::{CtlData, CtlZData},
lookup::{GrandProductChallenge, GrandProductChallengeSet},
lookup::{Filter,Column},
};
use env_logger::{
DEFAULT_FILTER_ENV,
Env,
try_init_from_env
};
#[allow(dead_code)]
/// Number of rounds in a Keccak permutation.
pub(crate) const NUM_ROUNDS: usize = 24;
/// Number of 64-bit elements in the Keccak permutation input.
pub(crate) const NUM_INPUTS: usize = 25;
fn ceil_div(a: usize, b: usize) -> usize {
(a + b - 1) / b
}
pub fn keccak_polygon_bench(size: usize) -> Result<()> {
// here input in in terms of block
let mut num_perms = 1;
if size > 25 * 64/8 {
num_perms = ceil_div(size, 25 * 64/8) as usize;
}
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = KeccakStark<F, D>;
let stark = S::default();
let config = StarkConfig::standard_fast_config();
init_logger();
let input: Vec<([u64; NUM_INPUTS], usize)> =
(0..num_perms).map(|_| (rand::random(), 0)).collect();
let mut timing = TimingTree::new("prove", log::Level::Debug);
let trace_poly_values = timed!(
timing,
"generate trace",
stark.generate_trace(input, 8, &mut timing)
);
let cloned_trace_poly_values = timed!(timing, "clone", trace_poly_values.clone());
let trace_commitments = timed!(
timing,
"compute trace commitment",
PolynomialBatch::<F, C, D>::from_values(
cloned_trace_poly_values,
config.fri_config.rate_bits,
false,
config.fri_config.cap_height,
&mut timing,
None,
)
);
let degree = 1 << trace_commitments.degree_log;
// Fake CTL data.
let ctl_z_data = CtlZData::new(
vec![PolynomialValues::zero(degree)],
PolynomialValues::zero(degree),
GrandProductChallenge {
beta: F::ZERO,
gamma: F::ZERO,
},
vec![],
vec![Filter::new_simple(Column::constant(F::ZERO))],
);
let ctl_data = CtlData {
zs_columns: vec![ctl_z_data.clone(); config.num_challenges],
};
prove_single_table(
&stark,
&config,
&trace_poly_values,
&trace_commitments,
&ctl_data,
&GrandProductChallengeSet {
challenges: vec![ctl_z_data.challenge; config.num_challenges],
},
&mut Challenger::new(),
&mut timing,
None,
)?;
timing.print();
Ok(())
}
fn init_logger() {
let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "debug"));
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use plonky2::field::types::PrimeField64;
use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
use tiny_keccak::keccakf;
use evm_arithmetization::keccak::columns::reg_output_limb;
use super::*;
const NUM_ROUNDS: usize = 24;
#[test]
fn keccak_correctness_test() -> Result<()> {
let input: [u64; NUM_INPUTS] = rand::random();
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = KeccakStark<F, D>;
let stark: KeccakStark<<PoseidonGoldilocksConfig as GenericConfig<2>>::F, 2> = S::default();
let rows = stark.generate_trace_rows(vec![(input, 0)], 8);
let last_row = rows[NUM_ROUNDS - 1];
let output = (0..NUM_INPUTS)
.map(|i| {
let hi = last_row[reg_output_limb(2 * i + 1)].to_canonical_u64();
let lo = last_row[reg_output_limb(2 * i)].to_canonical_u64();
(hi << 32) | lo
})
.collect::<Vec<_>>();
let expected = {
let mut state = input;
keccakf(&mut state);
state
};
assert_eq!(output, expected);
Ok(())
}
}

View File

@ -0,0 +1,75 @@
use anyhow::Result;
use plonky2::{
field::types::Field,
field::goldilocks_field::GoldilocksField,
hash::poseidon::PoseidonHash,
iop::witness::{PartialWitness, WitnessWrite},
plonk::circuit_builder::CircuitBuilder,
plonk::circuit_data::CircuitConfig,
plonk::config::{GenericConfig, PoseidonGoldilocksConfig},
};
use rand::Rng;
use std::time;
fn generate_data(size: usize) -> Vec<GoldilocksField> {
let mut data: Vec<GoldilocksField> = Vec::new();
for _ in 0..(1<<size) {
let mut rng = rand::thread_rng();
let random_u64: u64 = rng.gen();
data.push(GoldilocksField::from_canonical_u64(random_u64));
}
data
}
pub fn poseidon_bench(depth: usize) -> Result<()> {
let data = generate_data(depth);
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
// The arithmetic circuit.
let initial = builder.add_virtual_targets(data.len());
let hash = builder.hash_or_noop::<PoseidonHash>(initial.clone());
// Public inputs are the initial value (provided below) and the result (which is generated).
builder.register_public_inputs(initial.clone().as_slice());
builder.register_public_input(hash.elements[0]);
builder.register_public_input(hash.elements[1]);
builder.register_public_input(hash.elements[2]);
builder.register_public_input(hash.elements[3]);
// Provide initial values.
let mut pw = PartialWitness::new();
pw.set_target_arr(initial.as_slice(), data.as_slice());
let data = builder.build::<C>();
let (proof_generation_time, proof) = {
let start = time::Instant::now();
let proof = data.prove(pw)?;
let end_time = start.elapsed();
(end_time, proof)
};
let (verification_time, result) = {
let start = time::Instant::now();
let result = data.verify(proof);
let end_time = start.elapsed();
(end_time, result)
};
eprintln!("proof generation time: {:?}", proof_generation_time);
eprintln!("verification time: {:?}", verification_time);
result
}

View File

@ -0,0 +1,26 @@
use plonky2::iop::target::BoolTarget;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::field::extension::Extendable;
use plonky2_u32::gadgets::arithmetic_u32::U32Target;
use crate::arithmetic::u32_arithmetic::{bits_to_u32_target, u32_to_bits_target};
pub fn ch<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
b: &U32Target,
c: &U32Target,
) -> U32Target {
let a_bits = u32_to_bits_target::<F, D, 2>(builder, a);
let b_bits = u32_to_bits_target::<F, D, 2>(builder, b);
let c_bits = u32_to_bits_target::<F, D, 2>(builder, c);
let mut res_bits = Vec::new();
for i in 0..32 {
let b_sub_c = builder.sub(b_bits[i].target, c_bits[i].target);
let a_mul_b_sub_c = builder.mul(a_bits[i].target, b_sub_c);
let a_mul_b_sub_c_add_c = builder.add(a_mul_b_sub_c, c_bits[i].target);
res_bits.push(BoolTarget::new_unsafe(a_mul_b_sub_c_add_c));
}
bits_to_u32_target(builder, res_bits)
}

View File

@ -0,0 +1,24 @@
pub const H: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
];
pub const K: [u32; 64] = [
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2
];

View File

@ -0,0 +1,31 @@
use plonky2::iop::target::BoolTarget;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::field::extension::Extendable;
use plonky2_u32::gadgets::arithmetic_u32::U32Target;
use crate::arithmetic::u32_arithmetic::{bits_to_u32_target, u32_to_bits_target};
pub fn maj<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
b: &U32Target,
c: &U32Target,
) -> U32Target {
let a_bits = u32_to_bits_target::<F, D, 2>(builder, a);
let b_bits = u32_to_bits_target::<F, D, 2>(builder, b);
let c_bits = u32_to_bits_target::<F, D, 2>(builder, c);
let mut res_bits = Vec::new();
for i in 0..32 {
let m = builder.mul(b_bits[i].target, c_bits[i].target);
let two = builder.two();
let two_m = builder.mul(two, m);
let b_add_c = builder.add(b_bits[i].target, c_bits[i].target);
let b_add_c_sub_two_m = builder.sub(b_add_c, two_m);
let a_mul_b_add_c_sub_two_m = builder.mul(a_bits[i].target, b_add_c_sub_two_m);
let res = builder.add(a_mul_b_add_c_sub_two_m, m);
res_bits.push(BoolTarget::new_unsafe(res));
}
bits_to_u32_target(builder, res_bits)
}

View File

@ -0,0 +1,245 @@
// code is taken from https://github.com/polymerdao/plonky2-sha256
use plonky2::iop::target::BoolTarget;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::field::extension::Extendable;
use plonky2_u32::gadgets::arithmetic_u32::{CircuitBuilderU32, U32Target};
use anyhow::Result;
use plonky2::plonk::config::PoseidonGoldilocksConfig;
use plonky2::plonk::config::GenericConfig;
use plonky2::plonk::circuit_data::CircuitConfig;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use sha2::{Digest, Sha256};
use super::sigma::big_sigma0;
use super::sigma::big_sigma1;
use super::sigma::sigma0;
use super::sigma::sigma1;
use super::maj::maj;
use super::constants::*;
use super::ch::ch;
use crate::arithmetic::u32_arithmetic::add_u32;
use rand::Rng;
pub struct Sha256Targets {
pub message: Vec<BoolTarget>,
pub digest: Vec<BoolTarget>,
}
pub fn array_to_bits(bytes: &[u8]) -> Vec<bool> {
let len = bytes.len();
let mut ret = Vec::new();
for i in 0..len {
for j in 0..8 {
let b = (bytes[i] >> (7 - j)) & 1;
ret.push(b == 1);
}
}
ret
}
pub fn make_circuits<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
msg_len_in_bits: u64,
) -> Sha256Targets {
let mut message = Vec::new();
let mut digest = Vec::new();
let block_count = (msg_len_in_bits + 65 + 511) / 512;
let padded_msg_len = 512 * block_count;
let p = padded_msg_len - 64 - msg_len_in_bits;
assert!(p > 1);
//msg
for _ in 0..msg_len_in_bits {
message.push(builder.add_virtual_bool_target_unsafe());
}
//append a single bit '1'
message.push(builder.constant_bool(true));
//append '0' bit so that total length become multiple of 512
for _ in 0..p - 1 {
message.push(builder.constant_bool(false));
}
//append the msg length as 64bit big-endian integer
for i in 0..64 {
let b = ((msg_len_in_bits as u64) >> (63 - i)) & 1;
message.push(builder.constant_bool(b == 1));
}
// init states
let mut state = Vec::new();
for i in 0..8 {
state.push(builder.constant_u32(H[i]));
}
let mut k256 = Vec::new();
for i in 0..64 {
k256.push(builder.constant_u32(K[i]));
}
for blk in 0..block_count {
let mut x = Vec::new();
let mut a = state[0].clone();
let mut b = state[1].clone();
let mut c = state[2].clone();
let mut d = state[3].clone();
let mut e = state[4].clone();
let mut f = state[5].clone();
let mut g = state[6].clone();
let mut h = state[7].clone();
for i in 0..16 {
let index = blk as usize * 512 + i * 32;
let u32_target = builder.le_sum(message[index..index + 32].iter().rev());
x.push(U32Target(u32_target));
let mut t1 = h.clone();
let big_sigma1_e = big_sigma1(builder, &e);
t1 = add_u32(builder, &t1, &big_sigma1_e);
let ch_e_f_g = ch(builder, &e, &f, &g);
t1 = add_u32(builder, &t1, &ch_e_f_g);
t1 = add_u32(builder, &t1, &k256[i]);
t1 = add_u32(builder, &t1, &x[i]);
let mut t2 = big_sigma0(builder, &a);
let maj_a_b_c = maj(builder, &a, &b, &c);
t2 = add_u32(builder, &t2, &maj_a_b_c);
h = g;
g = f;
f = e;
e = add_u32(builder, &d, &t1);
d = c;
c = b;
b = a;
a = add_u32(builder, &t1, &t2);
}
for i in 16..64 {
let s0 = sigma0(builder, &x[(i + 1) & 0x0f]);
let s1 = sigma1(builder, &x[(i + 14) & 0x0f]);
let s0_add_s1 = add_u32(builder, &s0, &s1);
let s0_add_s1_add_x = add_u32(builder, &s0_add_s1, &x[(i + 9) & 0xf]);
x[i & 0xf] = add_u32(builder, &x[i & 0xf], &s0_add_s1_add_x);
let big_sigma0_a = big_sigma0(builder, &a);
let big_sigma1_e = big_sigma1(builder, &e);
let ch_e_f_g = ch(builder, &e, &f, &g);
let maj_a_b_c = maj(builder, &a, &b, &c);
let h_add_sigma1 = add_u32(builder, &h, &big_sigma1_e);
let h_add_sigma1_add_ch_e_f_g = add_u32(builder, &h_add_sigma1, &ch_e_f_g);
let h_add_sigma1_add_ch_e_f_g_add_k256 =
add_u32(builder, &h_add_sigma1_add_ch_e_f_g, &k256[i]);
let t1 = add_u32(builder, &x[i & 0xf], &h_add_sigma1_add_ch_e_f_g_add_k256);
let t2 = add_u32(builder, &big_sigma0_a, &maj_a_b_c);
h = g;
g = f;
f = e;
e = add_u32(builder, &d, &t1);
d = c;
c = b;
b = a;
a = add_u32(builder, &t1, &t2);
}
state[0] = add_u32(builder, &state[0], &a);
state[1] = add_u32(builder, &state[1], &b);
state[2] = add_u32(builder, &state[2], &c);
state[3] = add_u32(builder, &state[3], &d);
state[4] = add_u32(builder, &state[4], &e);
state[5] = add_u32(builder, &state[5], &f);
state[6] = add_u32(builder, &state[6], &g);
state[7] = add_u32(builder, &state[7], &h);
}
for i in 0..8 {
let bit_targets = builder.split_le_base::<2>(state[i].0, 32);
for j in (0..32).rev() {
digest.push(BoolTarget::new_unsafe(bit_targets[j]));
}
}
Sha256Targets { message, digest }
}
fn generate_random_bytes(size: usize) -> Vec<u8> {
let mut rng = rand::thread_rng();
let mut bytes = vec![0u8; size];
rng.fill(&mut bytes[..]);
bytes
}
pub fn sha256_bench(size: usize) -> Result<()> {
let msg = generate_random_bytes(size);
let mut hasher = Sha256::new();
hasher.update(msg.clone());
let hash = hasher.finalize();
let msg_bits = array_to_bits(&msg.clone());
let len = msg.len() * 8;
println!("block count: {}", (len + 65 + 511) / 512);
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
let mut builder = CircuitBuilder::<F, D>::new(CircuitConfig::standard_recursion_config());
let targets = make_circuits(&mut builder, len as u64);
let mut pw = PartialWitness::new();
for i in 0..len {
pw.set_bool_target(targets.message[i], msg_bits[i]);
}
let expected_res = array_to_bits(hash.as_slice());
for i in 0..expected_res.len() {
if expected_res[i] {
builder.assert_one(targets.digest[i].target);
} else {
builder.assert_zero(targets.digest[i].target);
}
}
println!(
"number of gates: {}",
builder.num_gates()
);
let data = builder.build::<C>();
let (proof_time, proof ) = {
let start = std::time::Instant::now();
let proof = data.prove(pw).unwrap();
let end = start.elapsed();
(end, proof)
};
let proof_size = proof.to_bytes().len();
let (verification_time, res) = {
let start = std::time::Instant::now();
let res = data.verify(proof);
let end = start.elapsed();
(end, res)
};
eprintln!("Proof Generation Time: {:?}", proof_time);
eprintln!("Verification Time: {:?}", verification_time);
eprintln!("Proof size: {:?}", proof_size);
res
}

View File

@ -0,0 +1,91 @@
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::field::extension::Extendable;
use plonky2_u32::gadgets::arithmetic_u32::U32Target;
use crate::arithmetic::u32_arithmetic::{rotate32, shift32};
use crate::arithmetic::u32_arithmetic::u32_to_bits_target;
use crate::arithmetic::u32_arithmetic::bits_to_u32_target;
use super::xor3::xor3;
pub fn sigma0<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
) -> U32Target {
let mut a_bits = u32_to_bits_target::<F, D, 2>(builder, a);
a_bits.push(builder.constant_bool(false));
let rotate7 = rotate32(7);
let rotate18 = rotate32(18);
let shift3 = shift32(3);
let mut res_bits = Vec::new();
for i in 0..32 {
res_bits.push(xor3(
builder,
a_bits[rotate7[i]],
a_bits[rotate18[i]],
a_bits[shift3[i]],
));
}
bits_to_u32_target(builder, res_bits)
}
pub fn sigma1<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
) -> U32Target {
let mut a_bits = u32_to_bits_target::<F, D, 2>(builder, a);
a_bits.push(builder.constant_bool(false));
let rotate17 = rotate32(17);
let rotate19 = rotate32(19);
let shift10 = shift32(10);
let mut res_bits = Vec::new();
for i in 0..32 {
res_bits.push(xor3(
builder,
a_bits[rotate17[i]],
a_bits[rotate19[i]],
a_bits[shift10[i]],
));
}
bits_to_u32_target(builder, res_bits)
}
//#define Sigma0(x) (ROTATE((x), 2) ^ ROTATE((x),13) ^ ROTATE((x),22))
pub fn big_sigma0<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
) -> U32Target {
let a_bits = u32_to_bits_target::<F, D, 2>(builder, a);
let rotate2 = rotate32(2);
let rotate13 = rotate32(13);
let rotate22 = rotate32(22);
let mut res_bits = Vec::new();
for i in 0..32 {
res_bits.push(xor3(
builder,
a_bits[rotate2[i]],
a_bits[rotate13[i]],
a_bits[rotate22[i]],
));
}
bits_to_u32_target(builder, res_bits)
}
pub fn big_sigma1<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: &U32Target,
) -> U32Target {
let a_bits = u32_to_bits_target::<F, D, 2>(builder, a);
let rotate6 = rotate32(6);
let rotate11 = rotate32(11);
let rotate25 = rotate32(25);
let mut res_bits = Vec::new();
for i in 0..32 {
res_bits.push(xor3(
builder,
a_bits[rotate6[i]],
a_bits[rotate11[i]],
a_bits[rotate25[i]],
));
}
bits_to_u32_target(builder, res_bits)
}

View File

@ -0,0 +1,26 @@
use plonky2::iop::target::BoolTarget;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::field::extension::Extendable;
pub fn xor3<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
a: BoolTarget,
b: BoolTarget,
c: BoolTarget,
) -> BoolTarget {
let m = builder.mul(b.target, c.target);
let two_b = builder.add(b.target, b.target);
let two_c = builder.add(c.target, c.target);
let two_m = builder.add(m, m);
let four_m = builder.add(two_m, two_m);
let one = builder.one();
let one_sub_two_b = builder.sub(one, two_b);
let one_sub_two_b_sub_two_c = builder.sub(one_sub_two_b, two_c);
let one_sub_two_b_sub_two_c_add_four_m = builder.add(one_sub_two_b_sub_two_c, four_m);
let mut res = builder.mul(a.target, one_sub_two_b_sub_two_c_add_four_m);
res = builder.add(res, b.target);
res = builder.add(res, c.target);
BoolTarget::new_unsafe(builder.sub(res, two_m))
}

View File

@ -0,0 +1,77 @@
use std::process;
mod bench{
pub mod poseidon;
pub mod sha256{
pub mod constants;
pub mod sigma;
pub mod sha;
pub mod xor3;
pub mod maj;
pub mod ch;
}
pub mod keccak256{
pub mod keccak;
pub mod keccak_polygon;
}
}
mod arithmetic {
pub mod binary_arithmetic;
pub mod u32_arithmetic;
pub mod u64_arithmetic;
}
use bench::poseidon::poseidon_bench;
use bench::keccak256::keccak::keccak_bench;
use bench::sha256::sha::sha256_bench;
use bench::keccak256::keccak_polygon::keccak_polygon_bench;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() != 3 {
println!("Wrong number of arguments! The program expects two arguments: <hash_type> and <size>");
// Exit the program with a non-zero exit code
process::exit(1);
}
let hash_type = &args[1];
let size = args[2].parse::<usize>().unwrap();
match hash_type.as_str() {
"poseidon" => {
println!("Running Poseidon: ");
eprintln!("Tree Depth: {:?}", size);
let _ = poseidon_bench(size);
}
"keccak" => {
println!("Running keccak: ");
eprintln!("input size: {:?}", size);
let _ = keccak_bench(size);
}
"keccak-polygon" => {
println!("Running keccak of plolygon zk_evm: ");
let _ = keccak_polygon_bench(size);
}
"sha256" => {
println!("Running sha256: ");
let _ = sha256_bench(size);
}
_ => {
println!("Wrong Benchmark Name!");
}
}
println!("All Done!");
}

1
hash/plonky2/external/zk_evm vendored Submodule

@ -0,0 +1 @@
Subproject commit 5511569d166c29cc85211df31bb9485870ca53ab

@ -1 +1 @@
Subproject commit e6b99b20f038f27390f590313ce7de227d6dd42a
Subproject commit 3ae1517526f1061a8d37a159270cc15727e6b503