├── .gitignore ├── ptau └── ppot_0080_01.ptau.test ├── src ├── lib.rs ├── vec.rs ├── enc.rs ├── kem.rs ├── kzg.rs └── kzg │ └── ptau.rs ├── Cargo.toml ├── README.md ├── LICENSE ├── .github └── workflows │ ├── coverage.yml │ └── build.yml ├── tests └── laconic_ot.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | /ptau/*.ptau 4 | /.DS_Store 5 | -------------------------------------------------------------------------------- /ptau/ppot_0080_01.ptau.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brech1/keaki/HEAD/ptau/ppot_0080_01.ptau.test -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Keaki 2 | //! 3 | //! Implementation of an Extractable Witness Encryption for KZG Commitments scheme. 4 | 5 | pub mod enc; 6 | pub mod kem; 7 | pub mod kzg; 8 | pub mod vec; 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keaki" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | # Arkworks 8 | ark-bls12-381 = { version = "0.4.0", default-features = false, features = ["curve"] } 9 | ark-bn254 = { version = "0.4.0", default-features = false, features = ["curve"] } 10 | ark-ec = { version = "0.4.2", default-features = false } 11 | ark-ff = { version = "0.4.2", default-features = false } 12 | ark-poly = { version = "0.4.2", default-features = false } 13 | ark-serialize = { version = "0.4.2", default-features = false } 14 | ark-std = { version = "0.4.0", default-features = false } 15 | 16 | # Error 17 | thiserror = { version = "1.0.64" } 18 | 19 | # Hashing 20 | blake3 = { version = "1.5.4", default-features = false } 21 | 22 | # Rng 23 | getrandom = { version = "0.2.5", default-features = false, features = ["js"] } 24 | rand = { version = "0.8.5", default-features = false, features = ["small_rng", "getrandom"] } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ケヤキ 🌳 2 | 3 | [![License][mit-badge]][mit-url] 4 | [![Build][actions-badge]][actions-url] 5 | [![Codecov][codecov-badge]][codecov-url] 6 | 7 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 8 | [mit-url]: https://github.com/brech1/keaki/blob/master/LICENSE 9 | [actions-badge]: https://github.com/brech1/keaki/actions/workflows/build.yml/badge.svg 10 | [actions-url]: https://github.com/brech1/keaki/actions?query=branch%3Amaster 11 | [codecov-badge]: https://codecov.io/github/brech1/keaki/graph/badge.svg 12 | [codecov-url]: https://app.codecov.io/github/brech1/keaki/ 13 | 14 | **Keaki** is a Rust implementation of an Extractable Witness Encryption for KZG Commitments scheme. 15 | 16 | Based on the following paper: 17 | 18 | - [Extractable Witness Encryption for KZG Commitments and Efficient Laconic OT](https://eprint.iacr.org/2024/264) 19 | 20 | A great post on it can be found here: 21 | 22 | - [Notes on Extractable Witness Encryption for KZG Commitments and Efficient Laconic OT](https://www.leku.blog/kzg-we) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Brechy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 14 | 15 | jobs: 16 | coverage: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - uses: actions/cache@v3 23 | with: 24 | path: | 25 | ~/.cargo 26 | ~/.rustup/toolchains 27 | target 28 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-cargo- 31 | 32 | - name: Install Rust 33 | run: rustup toolchain install stable 34 | 35 | - name: Install cargo-llvm-cov 36 | uses: taiki-e/install-action@cargo-llvm-cov 37 | 38 | - name: Generate code coverage 39 | run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info 40 | 41 | - name: Upload coverage to Codecov 42 | uses: codecov/codecov-action@v4 43 | with: 44 | files: lcov.info 45 | fail_ci_if_error: true 46 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - uses: actions/cache@v3 22 | with: 23 | path: | 24 | ~/.cargo 25 | ~/.rustup/toolchains 26 | target 27 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 28 | restore-keys: | 29 | ${{ runner.os }}-cargo- 30 | 31 | - name: Install Rust and wasm32 target 32 | run: | 33 | rustup toolchain install stable 34 | rustup target add wasm32-unknown-unknown 35 | 36 | - name: Build 37 | run: cargo build --verbose 38 | 39 | - name: Build wasm32 40 | run: cargo build --target wasm32-unknown-unknown --verbose 41 | 42 | - name: Clippy 43 | run: cargo clippy --verbose -- -D warnings 44 | 45 | - name: Tests 46 | run: cargo test --verbose 47 | 48 | - name: Fmt 49 | run: cargo fmt -- --check 50 | -------------------------------------------------------------------------------- /src/vec.rs: -------------------------------------------------------------------------------- 1 | //! # Vector 2 | //! 3 | //! This module provides helper functions for vector operations. 4 | 5 | use crate::{ 6 | enc::{decrypt, encrypt, Ciphertext}, 7 | kzg::{commit, open_fk, KZGSetup}, 8 | }; 9 | use ark_ec::pairing::Pairing; 10 | use ark_poly::{ 11 | domain::EvaluationDomain, polynomial::univariate::DensePolynomial, DenseUVPolynomial, 12 | Radix2EvaluationDomain, 13 | }; 14 | use ark_std::UniformRand; 15 | 16 | /// Padding length. 17 | /// Padding the vector with randomness is necessary since the commitment could leak information. 18 | pub const PADDING_LEN: usize = 1; 19 | 20 | /// Vector commitment. 21 | /// Returns the commitment and the proofs for each index. 22 | pub fn vec_commit( 23 | rng: &mut impl rand::Rng, 24 | kzg_setup: &KZGSetup, 25 | vec: &[E::ScalarField], 26 | ) -> Result<(E::G1, Vec), &'static str> { 27 | let d = vec.len() + PADDING_LEN; 28 | let mut padded_vec = Vec::with_capacity(d); 29 | padded_vec.extend_from_slice(vec); 30 | 31 | // Insert random value 32 | let r = E::ScalarField::rand(rng); 33 | padded_vec.push(r); 34 | 35 | // Transform the vector into a polynomial in coefficient form 36 | let domain = Radix2EvaluationDomain::<::ScalarField>::new(d).unwrap(); 37 | let p_coeff = domain.ifft(&padded_vec); 38 | 39 | // Calculate proofs for each index 40 | let proofs = open_fk(kzg_setup, &p_coeff, &domain).unwrap(); 41 | 42 | // Construct the dense polynomial from the coefficients 43 | let p_dense = DensePolynomial::from_coefficients_vec(p_coeff); 44 | 45 | // Commit 46 | let com = commit(kzg_setup, &p_dense).unwrap(); 47 | 48 | Ok((com, proofs)) 49 | } 50 | 51 | /// Vector Encryption. 52 | pub fn vec_encrypt( 53 | rng: &mut impl rand::Rng, 54 | kzg_setup: &KZGSetup, 55 | com: E::G1, 56 | points: &[E::ScalarField], 57 | values: &[E::ScalarField], 58 | messages: &[&[u8]], 59 | ) -> Vec> { 60 | let len = messages.len(); 61 | let mut cts = Vec::with_capacity(len); 62 | 63 | for i in 0..len { 64 | let ct = encrypt::(rng, kzg_setup, com, points[i], values[i], messages[i]); 65 | cts.push(ct); 66 | } 67 | 68 | cts 69 | } 70 | 71 | /// Vector Decryption. 72 | pub fn vec_decrypt(proofs: &[E::G1], cts: &[&Ciphertext]) -> Vec> { 73 | let mut decrypted_messages = Vec::with_capacity(cts.len()); 74 | 75 | for i in 0..cts.len() { 76 | let decrypted_message = decrypt::(proofs[i], cts[i]); 77 | decrypted_messages.push(decrypted_message); 78 | } 79 | 80 | decrypted_messages 81 | } 82 | -------------------------------------------------------------------------------- /src/enc.rs: -------------------------------------------------------------------------------- 1 | //! # Encryption 2 | //! 3 | //! This module provides functions for encrypting and decrypting messages based on the KEM. 4 | 5 | use crate::{ 6 | kem::{decapsulate, encapsulate}, 7 | kzg::KZGSetup, 8 | }; 9 | use ark_ec::pairing::Pairing; 10 | use ark_std::vec::Vec; 11 | 12 | /// Ciphertext type alias. 13 | pub type Ciphertext = (::G2, Vec); 14 | 15 | /// Encrypts a message using a commitment, point, and value. 16 | /// Returns two ciphertexts: 17 | /// - `key_ct`: used to generate the decryption key. 18 | /// - `msg_ct`: the encrypted message. 19 | pub fn encrypt( 20 | rng: &mut impl rand::Rng, 21 | kzg_setup: &KZGSetup, 22 | com: E::G1, 23 | point: E::ScalarField, 24 | value: E::ScalarField, 25 | msg: &[u8], 26 | ) -> Ciphertext { 27 | // Generate a key and the corresponding key ciphertext 28 | // (ct_1, k) <- Encap(x) 29 | let (key_ct, key) = encapsulate::(rng, kzg_setup, com, point, value, msg.len()); 30 | 31 | // ct_2 <- Enc(k, m) 32 | let mut msg_ct = vec![0u8; msg.len()]; 33 | msg_ct 34 | .iter_mut() 35 | .zip(key.iter().zip(msg.iter())) 36 | .for_each(|(out, (&k, &m))| *out = k ^ m); 37 | 38 | // (ct_1, ct_2) 39 | (key_ct, msg_ct) 40 | } 41 | 42 | /// Decrypts a ciphertext with a proof. 43 | /// Returns the decrypted message. 44 | pub fn decrypt(proof: E::G1, ct: &Ciphertext) -> Vec { 45 | let mut key = decapsulate::(proof, ct.0, ct.1.len()); 46 | 47 | // Decrypt 48 | let msg: Vec = key 49 | .iter_mut() 50 | .zip(ct.1.iter()) 51 | .map(|(k, &c)| *k ^ c) 52 | .collect(); 53 | 54 | msg 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use crate::kzg::{commit, open, KZGSetup}; 61 | use ark_bls12_381::{Bls12_381, Fr}; 62 | use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; 63 | use ark_std::{rand::Rng, test_rng, UniformRand}; 64 | 65 | fn setup_kzg(rng: &mut impl Rng) -> KZGSetup { 66 | let secret = Fr::rand(rng); 67 | KZGSetup::::setup(secret, 10) 68 | } 69 | 70 | #[test] 71 | fn test_encrypt() { 72 | let rng = &mut test_rng(); 73 | let kzg_setup = setup_kzg(rng); 74 | 75 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 76 | let p = DensePolynomial::from_coefficients_slice(&[ 77 | Fr::from(-24), 78 | Fr::from(-25), 79 | Fr::from(-5), 80 | Fr::from(9), 81 | Fr::from(7), 82 | ]); 83 | 84 | let point: Fr = Fr::rand(rng); 85 | let val = p.evaluate(&point); 86 | let commitment = commit(&kzg_setup, &p).unwrap(); 87 | 88 | let msg = b"helloworld"; 89 | 90 | let ct = encrypt::(rng, &kzg_setup, commitment, point, val, msg); 91 | 92 | let proof = open(&kzg_setup, &p, &point).unwrap(); 93 | 94 | let decrypted_msg = decrypt::(proof, &ct); 95 | 96 | assert_eq!(msg.to_vec(), decrypted_msg); 97 | } 98 | 99 | #[test] 100 | fn test_decrypt_invalid_proof() { 101 | let rng = &mut test_rng(); 102 | let kzg_setup = setup_kzg(rng); 103 | 104 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 105 | let p = DensePolynomial::from_coefficients_slice(&[ 106 | Fr::from(-24), 107 | Fr::from(-25), 108 | Fr::from(-5), 109 | Fr::from(9), 110 | Fr::from(7), 111 | ]); 112 | 113 | let point: Fr = Fr::rand(rng); 114 | let val = p.evaluate(&point); 115 | let commitment = commit(&kzg_setup, &p).unwrap(); 116 | let msg = b"helloworld"; 117 | let ct = encrypt::(rng, &kzg_setup, commitment, point, val, msg); 118 | 119 | let wrong_point: Fr = Fr::rand(rng); 120 | let invalid_proof = open(&kzg_setup, &p, &wrong_point).unwrap(); 121 | 122 | let decrypted_msg = decrypt::(invalid_proof, &ct); 123 | 124 | assert_ne!(msg.to_vec(), decrypted_msg); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tests/laconic_ot.rs: -------------------------------------------------------------------------------- 1 | //! # Laconic OT 2 | //! 3 | //! This module contains the implementation of a Laconic Oblivious Transfer using we-kzg. 4 | 5 | use ark_ec::pairing::Pairing; 6 | use ark_ff::Field; 7 | use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; 8 | use keaki::{ 9 | enc::Ciphertext, 10 | kzg::KZGSetup, 11 | vec::{vec_commit, vec_decrypt, vec_encrypt, PADDING_LEN}, 12 | }; 13 | use std::time::Instant; 14 | 15 | pub struct Receiver { 16 | /// Receiver's choices 17 | choices: Vec, 18 | /// Commitment 19 | commitment: E::G1, 20 | /// Precomputed proofs 21 | proofs: Vec, 22 | } 23 | 24 | impl Receiver { 25 | pub fn new( 26 | kzg_setup: KZGSetup, 27 | rng: &mut impl rand::Rng, 28 | choices: Vec, 29 | ) -> Self { 30 | let (commitment, proofs) = vec_commit(rng, &kzg_setup, &choices).unwrap(); 31 | 32 | Self { 33 | choices, 34 | commitment, 35 | proofs, 36 | } 37 | } 38 | 39 | pub fn receive( 40 | &self, 41 | encrypted_sets: Vec>>, 42 | ) -> Result>, &'static str> { 43 | let n_choices = encrypted_sets[0].len(); 44 | let mut chosen_cts = Vec::with_capacity(n_choices); 45 | 46 | for i in 0..n_choices { 47 | if self.choices[i] == E::ScalarField::ZERO { 48 | chosen_cts.push(&encrypted_sets[0][i]); 49 | } else { 50 | chosen_cts.push(&encrypted_sets[1][i]); 51 | } 52 | } 53 | 54 | let decrypted_messages = vec_decrypt::(&self.proofs, &chosen_cts); 55 | 56 | Ok(decrypted_messages) 57 | } 58 | } 59 | 60 | pub struct Sender { 61 | /// KZG Setup 62 | kzg_setup: KZGSetup, 63 | /// Commitment 64 | commitment: E::G1, 65 | } 66 | 67 | impl Sender { 68 | pub fn new(kzg_setup: KZGSetup, commitment: E::G1) -> Self { 69 | Self { 70 | kzg_setup, 71 | commitment, 72 | } 73 | } 74 | 75 | pub fn send( 76 | &self, 77 | rng: &mut impl rand::Rng, 78 | private_set: &[Vec>], 79 | ) -> Result>>, &'static str> { 80 | let n_values = private_set[0].len(); 81 | let elements = 82 | Radix2EvaluationDomain::<::ScalarField>::new(n_values + PADDING_LEN) 83 | .unwrap() 84 | .elements() 85 | .collect::>(); 86 | 87 | let mut encrypted_messages = Vec::with_capacity(private_set.len()); 88 | 89 | let data0: Vec<&[u8]> = private_set[0].iter().map(|v| v.as_slice()).collect(); 90 | let ct_0 = vec_encrypt::( 91 | rng, 92 | &self.kzg_setup, 93 | self.commitment, 94 | &elements, 95 | &vec![E::ScalarField::ZERO; n_values], 96 | &data0, 97 | ); 98 | encrypted_messages.push(ct_0); 99 | 100 | let data1: Vec<&[u8]> = private_set[1].iter().map(|v| v.as_slice()).collect(); 101 | let ct_1 = vec_encrypt::( 102 | rng, 103 | &self.kzg_setup, 104 | self.commitment, 105 | &elements, 106 | &vec![E::ScalarField::ONE; n_values], 107 | &data1, 108 | ); 109 | encrypted_messages.push(ct_1); 110 | 111 | Ok(encrypted_messages) 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod laconic_ot_tests { 117 | use super::*; 118 | use ark_bls12_381::{Bls12_381, Fr}; 119 | use ark_std::{rand::Rng, test_rng, UniformRand}; 120 | 121 | const SETUP_DEGREE: usize = 16; 122 | const N_CHOICES: usize = 8; 123 | const CHOICE_CARDINALITY: usize = 2; 124 | const VALUE_BYTES: usize = 32; 125 | 126 | #[test] 127 | fn test_laconic_ot() { 128 | // Setup KZG commitment scheme 129 | let rng = &mut test_rng(); 130 | let secret = Fr::rand(rng); 131 | let kzg = KZGSetup::::setup(secret, SETUP_DEGREE); 132 | 133 | // -------------------- 134 | // ----- Receiver ----- 135 | // -------------------- 136 | 137 | // In oblivious transfer, the receiver chooses a value from a set. 138 | // In Laconic OT, the receiver chooses a value for each index. 139 | let choices: Vec = (0..N_CHOICES) 140 | .map(|_| if rng.gen_bool(0.5) { Fr::ZERO } else { Fr::ONE }) 141 | .collect(); 142 | 143 | let com_start = Instant::now(); 144 | 145 | let receiver = Receiver::new(kzg.clone(), rng, choices.clone()); 146 | 147 | let com_time = com_start.elapsed(); 148 | println!("Commitment + proofs time: {:?}", com_time); 149 | 150 | // -------------------- 151 | // ------ Sender ------ 152 | // -------------------- 153 | 154 | // The sender holds a private set, for which the receiver should only get to know the selected values, 155 | // and the sender should not know which values the receiver chose. 156 | let mut private_set: Vec>> = Vec::new(); 157 | for _ in 0..CHOICE_CARDINALITY { 158 | let mut choice_n = Vec::new(); 159 | 160 | for _ in 0..N_CHOICES { 161 | let choice: Vec = (0..VALUE_BYTES).map(|_| rng.gen()).collect(); 162 | choice_n.push(choice); 163 | } 164 | 165 | private_set.push(choice_n); 166 | } 167 | 168 | let sender = Sender::new(kzg.clone(), receiver.commitment.clone()); 169 | 170 | // Encrypt the set using the receiver's commitment 171 | let sender_send_start = Instant::now(); 172 | 173 | let encrypted_messages = sender.send(rng, &private_set).unwrap(); 174 | 175 | let sender_send_time = sender_send_start.elapsed(); 176 | println!("Sender encrypt time: {:?}\n", sender_send_time); 177 | 178 | // -------------------- 179 | // ----- Receiver ----- 180 | // -------------------- 181 | 182 | // Decrypt the pairs of ciphertexts using the receiver's boolean choice 183 | let receiver_receive_start = Instant::now(); 184 | 185 | let decrypted_messages = receiver.receive(encrypted_messages).unwrap(); 186 | 187 | let receiver_receive_time = receiver_receive_start.elapsed(); 188 | println!("Receiver decrypt time: {:?}\n", receiver_receive_time); 189 | 190 | // Verify correctness of decrypted messages 191 | for (i, decrypted_message) in decrypted_messages.iter().enumerate() { 192 | let expected_value = if choices[i] == Fr::ZERO { 193 | &private_set[0][i] 194 | } else { 195 | &private_set[1][i] 196 | }; 197 | 198 | assert_eq!(decrypted_message, expected_value, "Mismatch at index {}", i); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/kem.rs: -------------------------------------------------------------------------------- 1 | //! # Key Encapsulation Mechanism 2 | //! 3 | //! This module implements the **Extractable Witness Key Encapsulation Mechanism** functions. 4 | 5 | use crate::kzg::KZGSetup; 6 | use ark_ec::pairing::Pairing; 7 | use ark_ec::AffineRepr; 8 | use ark_serialize::CanonicalSerialize; 9 | use ark_std::{ops::Mul, vec::Vec, UniformRand}; 10 | 11 | /// Encapsulation. 12 | /// Generates a key for a commitment and a point-value pair. 13 | pub fn encapsulate( 14 | rng: &mut impl rand::Rng, 15 | kzg_setup: &KZGSetup, 16 | commitment: E::G1, 17 | point: E::ScalarField, 18 | value: E::ScalarField, 19 | msg_len: usize, 20 | ) -> (E::G2, Vec) { 21 | // (com - [beta]_1) 22 | let com_beta = commitment - E::G1Affine::generator().mul(value); 23 | 24 | // Generate a random value 25 | // This allows the generated secret not to be tied to the inputs. 26 | let r = E::ScalarField::rand(rng); 27 | 28 | // Calculate secret 29 | // s = e(r * (com - [beta]_1), g2) 30 | let secret = E::pairing(com_beta.mul(r), E::G2Affine::generator()); 31 | let mut secret_bytes = Vec::::new(); 32 | secret.serialize_uncompressed(&mut secret_bytes).unwrap(); 33 | 34 | // Calculate a ciphertext to share the randomness used in the encapsulation. 35 | // ct = r([tau]_2 - [alpha]_2) 36 | let tau_alpha: E::G2 = *kzg_setup.tau_g2() - E::G2Affine::generator().mul(point); 37 | let ciphertext: E::G2 = tau_alpha.mul(r); 38 | 39 | // Generate the key 40 | // Hash the secret to make the key indistinguishable from random. 41 | // k = H(s) 42 | let mut hasher = blake3::Hasher::new(); 43 | hasher.update(&secret_bytes); 44 | 45 | let mut key = vec![0u8; msg_len]; 46 | hasher.finalize_xof().fill(key.as_mut_slice()); 47 | 48 | // (ct, k) 49 | (ciphertext, key) 50 | } 51 | 52 | /// Decapsulation. 53 | /// Generates a key for an opening and a ciphertext. 54 | /// The generated key will be the same as the one generated during encapsulation for a valid opening. 55 | pub fn decapsulate(proof: E::G1, ciphertext: E::G2, msg_len: usize) -> Vec { 56 | // Calculate secret 57 | // s = e(proof, ct) 58 | let secret = E::pairing(proof, ciphertext); 59 | 60 | let mut secret_bytes = Vec::::new(); 61 | secret.serialize_uncompressed(&mut secret_bytes).unwrap(); 62 | 63 | // Get the key 64 | // k = H(s) 65 | let mut hasher = blake3::Hasher::new(); 66 | hasher.update(&secret_bytes); 67 | 68 | let mut key = vec![0u8; msg_len]; 69 | hasher.finalize_xof().fill(key.as_mut_slice()); 70 | 71 | key 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | use crate::kzg::{commit, open, KZGSetup}; 78 | use ark_bls12_381::{Bls12_381, Fr}; 79 | use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; 80 | use ark_std::{rand::Rng, test_rng}; 81 | 82 | fn setup_kzg(rng: &mut impl Rng) -> KZGSetup { 83 | let secret = Fr::rand(rng); 84 | KZGSetup::::setup(secret, 10) 85 | } 86 | 87 | #[test] 88 | fn test_encapsulation_decapsulation() { 89 | let rng = &mut test_rng(); 90 | let kzg_setup = setup_kzg(rng); 91 | 92 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 93 | let p = DensePolynomial::from_coefficients_slice(&[ 94 | Fr::from(-24), 95 | Fr::from(-25), 96 | Fr::from(-5), 97 | Fr::from(9), 98 | Fr::from(7), 99 | ]); 100 | 101 | let point: Fr = Fr::rand(rng); 102 | let eval = p.evaluate(&point); 103 | let commitment = commit(&kzg_setup, &p).unwrap(); 104 | let test_msg = [0u8; 32]; 105 | 106 | // Encapsulate 107 | let (ciphertext, enc_key) = 108 | encapsulate(rng, &kzg_setup, commitment, point, eval, test_msg.len()); 109 | 110 | // Decapsulate 111 | let proof = open(&kzg_setup, &p, &point).unwrap(); 112 | let dec_key = decapsulate::(proof, ciphertext, test_msg.len()); 113 | 114 | // Assert that the keys match 115 | assert_eq!(enc_key, dec_key); 116 | } 117 | 118 | #[test] 119 | fn test_decapsulation_invalid_proof() { 120 | let rng = &mut test_rng(); 121 | let kzg_setup = setup_kzg(rng); 122 | 123 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 124 | let p = DensePolynomial::from_coefficients_slice(&[ 125 | Fr::from(-24), 126 | Fr::from(-25), 127 | Fr::from(-5), 128 | Fr::from(9), 129 | Fr::from(7), 130 | ]); 131 | 132 | let point: Fr = Fr::rand(rng); 133 | let eval = p.evaluate(&point); 134 | let commitment = commit(&kzg_setup, &p).unwrap(); 135 | let test_msg = [0u8; 32]; 136 | 137 | // Encapsulate 138 | let (ciphertext, enc_key) = 139 | encapsulate(rng, &kzg_setup, commitment, point, eval, test_msg.len()); 140 | 141 | // Decapsulate with a different polynomial 142 | // q(x) = 7 x^4 + 9 x^3 - 5 x^2 - 29 x - 24 143 | let q = DensePolynomial::from_coefficients_slice(&[ 144 | Fr::from(-24), 145 | Fr::from(-29), 146 | Fr::from(-5), 147 | Fr::from(9), 148 | Fr::from(7), 149 | ]); 150 | 151 | let invalid_proof = open(&kzg_setup, &q, &point).unwrap(); 152 | let dec_key = decapsulate::(invalid_proof, ciphertext, test_msg.len()); 153 | 154 | // Keys should not match 155 | assert_ne!(enc_key, dec_key); 156 | } 157 | 158 | #[test] 159 | fn test_decapsulation_invalid_ciphertext() { 160 | let rng = &mut test_rng(); 161 | let kzg_setup = setup_kzg(rng); 162 | 163 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 164 | let p = DensePolynomial::from_coefficients_slice(&[ 165 | Fr::from(-24), 166 | Fr::from(-25), 167 | Fr::from(-5), 168 | Fr::from(9), 169 | Fr::from(7), 170 | ]); 171 | let point: Fr = Fr::rand(rng); 172 | let eval = p.evaluate(&point); 173 | let commitment = commit(&kzg_setup, &p).unwrap(); 174 | let test_msg = [0u8; 32]; 175 | 176 | // Encapsulate 177 | let (ciphertext, enc_key) = 178 | encapsulate(rng, &kzg_setup, commitment, point, eval, test_msg.len()); 179 | 180 | // Generate an random ciphertext 181 | let invalid_ciphertext = ciphertext.mul(Fr::rand(rng)); 182 | 183 | // Attempt to decapsulate with the invalid ciphertext 184 | let proof = open(&kzg_setup, &p, &point).unwrap(); 185 | 186 | let dec_key = decapsulate::(proof, invalid_ciphertext, test_msg.len()); 187 | 188 | // Keys should not match 189 | assert_ne!(enc_key, dec_key); 190 | } 191 | 192 | #[test] 193 | fn test_decapsulation_wrong_point() { 194 | let rng = &mut test_rng(); 195 | let kzg_setup = setup_kzg(rng); 196 | 197 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 198 | let p = DensePolynomial::from_coefficients_slice(&[ 199 | Fr::from(-24), 200 | Fr::from(-25), 201 | Fr::from(-5), 202 | Fr::from(9), 203 | Fr::from(7), 204 | ]); 205 | 206 | let point1: Fr = Fr::rand(rng); 207 | let val1 = p.evaluate(&point1); 208 | let commitment = commit(&kzg_setup, &p).unwrap(); 209 | let test_msg = [0u8; 32]; 210 | 211 | // Encapsulate with point1 212 | let (ciphertext1, enc_key) = 213 | encapsulate(rng, &kzg_setup, commitment, point1, val1, test_msg.len()); 214 | 215 | // Proof for point2 216 | let point2: Fr = Fr::rand(rng); 217 | let proof2 = open(&kzg_setup, &p, &point2).unwrap(); 218 | 219 | // Decapsulate with proof for point2 220 | let dec_key = decapsulate::(proof2, ciphertext1, test_msg.len()); 221 | 222 | // Keys should not match 223 | assert_ne!(enc_key, dec_key); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/kzg.rs: -------------------------------------------------------------------------------- 1 | //! # KZG Polynomial Commitment Scheme 2 | //! 3 | //! This module contains the implementation of the KZG polynomial commitment scheme. 4 | 5 | pub mod ptau; 6 | 7 | use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, VariableBaseMSM}; 8 | use ark_ff::{Field, Zero}; 9 | use ark_poly::{ 10 | univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, Polynomial, 11 | Radix2EvaluationDomain, 12 | }; 13 | use ark_std::{ 14 | ops::{Div, Mul, Sub}, 15 | vec::Vec, 16 | }; 17 | use ptau::{get_powers_from_file, SetupFileError}; 18 | use thiserror::Error; 19 | 20 | /// KZG Setup. 21 | #[derive(Debug, Clone, PartialEq, Eq)] 22 | pub struct KZGSetup { 23 | /// Powers of tau in G1 - [tau^i]_1 24 | g1_pow: Vec, 25 | /// Powers of tau in G1 - Affine representation 26 | g1_aff: Vec, 27 | /// [tau]_2 28 | tau_g2: E::G2, 29 | } 30 | 31 | impl KZGSetup { 32 | /// Setup from `ptau` file. 33 | pub fn new_from_file(file: &str) -> Result { 34 | let (g1_aff, g2_aff) = get_powers_from_file::(file)?; 35 | let tau_g2 = g2_aff 36 | .get(1) 37 | .copied() 38 | .ok_or(SetupFileError::EmptySection(3))? 39 | .into(); 40 | 41 | // Convert g1_aff to g1_pow 42 | let mut g1_pow: Vec<::G1> = Vec::with_capacity(g1_aff.len()); 43 | for &element in g1_aff.iter() { 44 | g1_pow.push(element.into()); 45 | } 46 | 47 | Ok(Self { 48 | g1_pow, 49 | g1_aff, 50 | tau_g2, 51 | }) 52 | } 53 | 54 | /// Setup from secret. Don't use this. 55 | pub fn setup(secret: E::ScalarField, max_d: usize) -> Self { 56 | let mut g1_pow: Vec<::G1> = Vec::with_capacity(max_d); 57 | let tau_g2 = E::G2Affine::generator().mul(secret); 58 | 59 | for i in 0..max_d { 60 | g1_pow.push(E::G1Affine::generator().mul(secret.pow([i as u64]))); 61 | } 62 | 63 | let g1_aff = E::G1::normalize_batch(&g1_pow); 64 | 65 | Self { 66 | g1_pow, 67 | g1_aff, 68 | tau_g2, 69 | } 70 | } 71 | 72 | /// Returns the powers of tau in G1. 73 | pub fn g1_pow(&self) -> &[E::G1] { 74 | &self.g1_pow 75 | } 76 | 77 | /// Returns the powers of tau in G1 - Affine representation. 78 | pub fn g1_aff(&self) -> &[E::G1Affine] { 79 | &self.g1_aff 80 | } 81 | 82 | /// Returns [tau]_2 83 | pub fn tau_g2(&self) -> &E::G2 { 84 | &self.tau_g2 85 | } 86 | } 87 | 88 | /// Commits to a polynomial. 89 | pub fn commit( 90 | setup: &KZGSetup, 91 | p: &DensePolynomial, 92 | ) -> Result { 93 | if p.len() > setup.g1_pow.len() { 94 | return Err(KZGError::PolynomialTooLarge(p.len(), setup.g1_pow.len())); 95 | } 96 | 97 | // commitment = sum(p[i] * g1_powers[i]) 98 | let com = ::msm_unchecked(&setup.g1_aff, p); 99 | 100 | Ok(com) 101 | } 102 | 103 | /// Computes an opening (proof) for a polynomial at a point. 104 | pub fn open( 105 | setup: &KZGSetup, 106 | p: &DensePolynomial, 107 | point: &E::ScalarField, 108 | ) -> Result { 109 | let value = p.evaluate(point); 110 | 111 | // p(point) 112 | let p_value = DensePolynomial::from_coefficients_slice(&[value]); 113 | 114 | // p(x) - p(point) 115 | let numerator = p.sub(&p_value); 116 | 117 | // x - point 118 | let denominator = DensePolynomial::from_coefficients_slice(&[-*point, E::ScalarField::ONE]); 119 | 120 | let quotient = numerator.div(&denominator); 121 | 122 | // Generate the proof by committing to the quotient polynomial 123 | commit(setup, "ient) 124 | } 125 | 126 | /// Verifies an opening proof for a polynomial commitment at a point. 127 | pub fn verify( 128 | setup: &KZGSetup, 129 | commitment: E::G1, 130 | point: E::ScalarField, 131 | value: E::ScalarField, 132 | proof: E::G1, 133 | ) -> Result { 134 | // [beta]_1 135 | let value_in_g1 = E::G1Affine::generator().mul(value); 136 | 137 | // [tau]_2 138 | let tau_in_g2 = setup.tau_g2; 139 | 140 | // [1]_2 141 | let g2_gen = E::G2Affine::generator(); 142 | 143 | // [alpha]_2 144 | let point_in_g2 = g2_gen.mul(point); 145 | 146 | // e(commitment - [beta]_1, [1]_2) == e(proof, [tau]_2 - [alpha]_2) 147 | let v = 148 | E::pairing(commitment - value_in_g1, g2_gen) == E::pairing(proof, tau_in_g2 - point_in_g2); 149 | 150 | Ok(v) 151 | } 152 | 153 | /// Computes n openings using the FK23 algorithm. 154 | /// The amount of openings will depend on the length of the polynomial. 155 | /// The openings points are the roots of unity of the domain. 156 | /// - `domain_d` is an evaluation domain of size d. 157 | pub fn open_fk( 158 | setup: &KZGSetup, 159 | p: &[E::ScalarField], 160 | domain_d: &Radix2EvaluationDomain, 161 | ) -> Result, KZGError> { 162 | let d = p.len(); 163 | let domain_2d = Radix2EvaluationDomain::<::ScalarField>::new(2 * d).unwrap(); 164 | 165 | // s = ([s[d−1]], [s[d−2]], ..., [s], [1], [0], [0], ..., [0]) 166 | // d neutral elements at the end 167 | let mut s: Vec<::G1> = vec![E::G1::zero(); 2 * d]; 168 | s[..d].copy_from_slice( 169 | &setup.g1_pow()[..d] 170 | .iter() 171 | .rev() 172 | .copied() 173 | .collect::>(), 174 | ); 175 | 176 | // a = (0, 0, ..., 0, f1, f2, ..., fd) 177 | // d neutral elements at the beginning 178 | let mut a: Vec<::ScalarField> = vec![E::ScalarField::zero(); 2 * d]; 179 | a[d..].copy_from_slice(p); 180 | 181 | // hat_s = DFT_2d(s) 182 | let hat_s = domain_2d.fft(&s); 183 | 184 | // hat_a = DFT_2d(a) 185 | let hat_a = domain_2d.fft(&a); 186 | 187 | // hat_h = hat_a * hat_s 188 | let mut hat_h: Vec = Vec::with_capacity(2 * d); 189 | for i in 0..2 * d { 190 | hat_h.push(hat_s[i].mul(hat_a[i])); 191 | } 192 | 193 | // hat_h = iDFt_2d(u) 194 | let h_prime = domain_2d.ifft(&hat_h); 195 | 196 | // Take first d elements of h_prime as h 197 | let h = h_prime[0..d].to_vec(); 198 | 199 | // Evaluate h in each n-th root of unity 200 | let ct = domain_d.fft(&h); 201 | 202 | Ok(ct) 203 | } 204 | 205 | #[derive(Error, Debug, PartialEq, Eq)] 206 | pub enum KZGError { 207 | #[error("Can't commit to polynomial: polynomial has degree {0} but max degree is {1}")] 208 | PolynomialTooLarge(usize, usize), 209 | } 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | use super::*; 214 | use ark_bls12_381::{Bls12_381, Fr, G1Projective}; 215 | use ark_ff::UniformRand; 216 | use ark_std::test_rng; 217 | 218 | #[test] 219 | fn test_kzg_setup() { 220 | let rng = &mut test_rng(); 221 | let secret = Fr::rand(rng); 222 | let max_degree = 4; 223 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 224 | 225 | // G1 226 | assert_eq!(kzg_setup.g1_pow.len(), max_degree); 227 | for i in 0..max_degree { 228 | assert_eq!( 229 | kzg_setup.g1_pow[i], 230 | ::G1Affine::generator().mul(secret.pow([i as u64])) 231 | ); 232 | } 233 | 234 | // [tau]_2 235 | assert_eq!( 236 | kzg_setup.tau_g2, 237 | ::G2Affine::generator().mul(secret) 238 | ); 239 | } 240 | 241 | #[test] 242 | fn test_kzg_commit() { 243 | let rng = &mut test_rng(); 244 | let secret = Fr::rand(rng); 245 | let max_degree = 4; 246 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 247 | 248 | // 2 x^2 + 3 x + 1 249 | let p = DensePolynomial::from_coefficients_slice(&[Fr::from(1), Fr::from(3), Fr::from(2)]); 250 | let commitment = commit(&kzg_setup, &p).unwrap(); 251 | 252 | let mut expected_commitment = G1Projective::zero(); 253 | for (i, &coeff) in p.iter().enumerate() { 254 | expected_commitment += kzg_setup.g1_pow[i] * coeff; 255 | } 256 | 257 | assert_eq!(commitment, expected_commitment); 258 | } 259 | 260 | #[test] 261 | fn test_kzg_commit_polynomial_too_large() { 262 | let rng = &mut test_rng(); 263 | let secret = Fr::rand(rng); 264 | let max_degree = 2; 265 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 266 | let p = DensePolynomial::from_coefficients_slice(&[ 267 | Fr::from(1), 268 | Fr::from(3), 269 | Fr::from(2), 270 | Fr::from(4), 271 | ]); 272 | 273 | let result = commit(&kzg_setup, &p); 274 | let expected_err = KZGError::PolynomialTooLarge(p.len(), max_degree); 275 | 276 | assert_eq!(result, Err(expected_err)); 277 | } 278 | 279 | #[test] 280 | fn test_kzg_open_polynomial_too_large() { 281 | let rng = &mut test_rng(); 282 | let secret = Fr::rand(rng); 283 | let max_degree = 2; 284 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 285 | let p = DensePolynomial::from_coefficients_slice(&[ 286 | Fr::from(1), 287 | Fr::from(2), 288 | Fr::from(3), 289 | Fr::from(4), 290 | Fr::from(5), 291 | Fr::from(6), 292 | ]); 293 | 294 | let point = Fr::from(5); 295 | 296 | let value = p.evaluate(&point); 297 | let numerator = p.sub(&DensePolynomial::from_coefficients_slice(&[value])); 298 | let denominator = DensePolynomial::from_coefficients_slice(&[ 299 | -point, 300 | ::ScalarField::ONE, 301 | ]); 302 | let quotient = numerator.div(&denominator); 303 | 304 | let result = open(&kzg_setup, &p, &point); 305 | let expected_err = KZGError::PolynomialTooLarge(quotient.len(), max_degree); 306 | 307 | assert_eq!(result, Err(expected_err)); 308 | } 309 | 310 | #[test] 311 | fn test_kzg_open_and_verify() { 312 | let rng = &mut test_rng(); 313 | let secret = Fr::rand(rng); 314 | let max_degree = 4; 315 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 316 | 317 | // 2 x^2 + 3 x + 1 318 | let p = DensePolynomial::from_coefficients_slice(&[Fr::from(1), Fr::from(3), Fr::from(2)]); 319 | 320 | let commitment = commit(&kzg_setup, &p).unwrap(); 321 | let point = Fr::from(5); 322 | 323 | // p(5) = 66 324 | let expected_value = Fr::from(66); 325 | 326 | let proof = open(&kzg_setup, &p, &point).unwrap(); 327 | 328 | let v = verify(&kzg_setup, commitment, point, expected_value, proof).unwrap(); 329 | 330 | assert!(v); 331 | } 332 | 333 | #[test] 334 | fn test_kzg_verify_wrong_alpha() { 335 | let rng = &mut test_rng(); 336 | let secret = Fr::rand(rng); 337 | let max_degree = 8; 338 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 339 | 340 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 341 | let p = DensePolynomial::from_coefficients_slice(&[ 342 | Fr::from(-24), 343 | Fr::from(-25), 344 | Fr::from(-5), 345 | Fr::from(9), 346 | Fr::from(7), 347 | ]); 348 | 349 | let commitment = commit(&kzg_setup, &p).unwrap(); 350 | let point = Fr::from(11); 351 | 352 | // p(11) = 113562 353 | let expected_value = Fr::from(113562); 354 | 355 | let proof = open(&kzg_setup, &p, &point).unwrap(); 356 | let v_valid_point = verify(&kzg_setup, commitment, point, expected_value, proof).unwrap(); 357 | assert_eq!(v_valid_point, true); 358 | 359 | let wrong_point = Fr::from(99); 360 | let v_wrong_point = 361 | verify(&kzg_setup, commitment, wrong_point, expected_value, proof).unwrap(); 362 | assert_eq!(v_wrong_point, false); 363 | } 364 | 365 | #[test] 366 | fn test_kzg_verify_wrong_beta() { 367 | let rng = &mut test_rng(); 368 | let secret = Fr::rand(rng); 369 | let max_degree = 8; 370 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 371 | 372 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 373 | let p = DensePolynomial::from_coefficients_slice(&[ 374 | Fr::from(-24), 375 | Fr::from(-25), 376 | Fr::from(-5), 377 | Fr::from(9), 378 | Fr::from(7), 379 | ]); 380 | 381 | let commitment = commit(&kzg_setup, &p).unwrap(); 382 | 383 | let point = Fr::from(6); 384 | 385 | // p(6) = 10662 386 | let expected_value = Fr::from(10662); 387 | 388 | let proof = open(&kzg_setup, &p, &point).unwrap(); 389 | let v_valid_point = verify(&kzg_setup, commitment, point, expected_value, proof).unwrap(); 390 | assert_eq!(v_valid_point, true); 391 | 392 | let wrong_value = Fr::from(10663); 393 | let v_wrong_value = verify(&kzg_setup, commitment, point, wrong_value, proof).unwrap(); 394 | assert_eq!(v_wrong_value, false); 395 | } 396 | 397 | #[test] 398 | fn test_kzg_verify_wrong_proof() { 399 | let rng = &mut test_rng(); 400 | let secret = Fr::rand(rng); 401 | let max_degree = 8; 402 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 403 | 404 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 405 | let p = DensePolynomial::from_coefficients_slice(&[ 406 | Fr::from(-24), 407 | Fr::from(-25), 408 | Fr::from(-5), 409 | Fr::from(9), 410 | Fr::from(7), 411 | ]); 412 | 413 | let commitment = commit(&kzg_setup, &p).unwrap(); 414 | 415 | let point = Fr::from(6); 416 | 417 | // p(6) = 10662 418 | let value = Fr::from(10662); 419 | 420 | let proof = open(&kzg_setup, &p, &point).unwrap(); 421 | let v_valid_point = verify(&kzg_setup, commitment, point, value, proof).unwrap(); 422 | assert_eq!(v_valid_point, true); 423 | 424 | // Create a proof for a different polynomial 425 | let fake_p = DensePolynomial::from_coefficients_slice(&[ 426 | Fr::from(-24), 427 | Fr::from(-26), 428 | Fr::from(-5), 429 | Fr::from(9), 430 | Fr::from(7), 431 | ]); 432 | let wrong_proof = open(&kzg_setup, &fake_p, &point).unwrap(); 433 | let v_wrong_proof = verify(&kzg_setup, commitment, point, value, wrong_proof).unwrap(); 434 | assert_eq!(v_wrong_proof, false); 435 | } 436 | 437 | #[test] 438 | fn test_kzg_verify_wrong_commitment() { 439 | let rng = &mut test_rng(); 440 | let secret = Fr::rand(rng); 441 | let max_degree = 8; 442 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 443 | 444 | // p(x) = 7 x^4 + 9 x^3 - 5 x^2 - 25 x - 24 445 | let p = DensePolynomial::from_coefficients_slice(&[ 446 | Fr::from(-24), 447 | Fr::from(-25), 448 | Fr::from(-5), 449 | Fr::from(9), 450 | Fr::from(7), 451 | ]); 452 | 453 | let commitment = commit(&kzg_setup, &p).unwrap(); 454 | 455 | let point = Fr::from(6); 456 | 457 | // p(6) = 10662 458 | let value = Fr::from(10662); 459 | 460 | let proof = open(&kzg_setup, &p, &point).unwrap(); 461 | let v_valid_commitment = verify(&kzg_setup, commitment, point, value, proof).unwrap(); 462 | assert_eq!(v_valid_commitment, true); 463 | 464 | // Create a random commitment 465 | let wrong_com = commitment.mul(Fr::from(2)); 466 | let v_wrong_commitment = verify(&kzg_setup, wrong_com, point, value, proof).unwrap(); 467 | assert_eq!(v_wrong_commitment, false); 468 | } 469 | 470 | #[test] 471 | fn test_kzg_open_fk() { 472 | let rng = &mut test_rng(); 473 | let secret = Fr::rand(rng); 474 | let max_degree = 16; 475 | let kzg_setup = KZGSetup::::setup(secret, max_degree); 476 | 477 | // Create commitment polynomial 478 | let p = DensePolynomial::from_coefficients_slice(&[ 479 | Fr::from(1), 480 | Fr::from(2), 481 | Fr::from(3), 482 | Fr::from(4), 483 | ]); 484 | 485 | // Calculate proofs 486 | let domain = 487 | Radix2EvaluationDomain::<::ScalarField>::new(p.len()).unwrap(); 488 | let proofs = open_fk(&kzg_setup, &p, &domain).unwrap(); 489 | 490 | // Create evaluation domain 491 | let domain = 492 | Radix2EvaluationDomain::<::ScalarField>::new(p.len()).unwrap(); 493 | let roots_of_unity = domain.elements(); 494 | 495 | // Open the polynomial at the evaluation points 496 | let mut expected_proofs = Vec::new(); 497 | for root in roots_of_unity { 498 | let proof = open(&kzg_setup, &p, &root).unwrap(); 499 | expected_proofs.push(proof); 500 | } 501 | 502 | for i in 0..p.len() { 503 | assert_eq!(proofs[i], expected_proofs[i]); 504 | } 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "ark-bls12-381" 19 | version = "0.4.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" 22 | dependencies = [ 23 | "ark-ec", 24 | "ark-ff", 25 | "ark-serialize", 26 | "ark-std", 27 | ] 28 | 29 | [[package]] 30 | name = "ark-bn254" 31 | version = "0.4.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" 34 | dependencies = [ 35 | "ark-ec", 36 | "ark-ff", 37 | "ark-std", 38 | ] 39 | 40 | [[package]] 41 | name = "ark-ec" 42 | version = "0.4.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" 45 | dependencies = [ 46 | "ark-ff", 47 | "ark-poly", 48 | "ark-serialize", 49 | "ark-std", 50 | "derivative", 51 | "hashbrown", 52 | "itertools", 53 | "num-traits", 54 | "zeroize", 55 | ] 56 | 57 | [[package]] 58 | name = "ark-ff" 59 | version = "0.4.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" 62 | dependencies = [ 63 | "ark-ff-asm", 64 | "ark-ff-macros", 65 | "ark-serialize", 66 | "ark-std", 67 | "derivative", 68 | "digest", 69 | "itertools", 70 | "num-bigint", 71 | "num-traits", 72 | "paste", 73 | "rustc_version", 74 | "zeroize", 75 | ] 76 | 77 | [[package]] 78 | name = "ark-ff-asm" 79 | version = "0.4.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" 82 | dependencies = [ 83 | "quote", 84 | "syn 1.0.109", 85 | ] 86 | 87 | [[package]] 88 | name = "ark-ff-macros" 89 | version = "0.4.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" 92 | dependencies = [ 93 | "num-bigint", 94 | "num-traits", 95 | "proc-macro2", 96 | "quote", 97 | "syn 1.0.109", 98 | ] 99 | 100 | [[package]] 101 | name = "ark-poly" 102 | version = "0.4.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" 105 | dependencies = [ 106 | "ark-ff", 107 | "ark-serialize", 108 | "ark-std", 109 | "derivative", 110 | "hashbrown", 111 | ] 112 | 113 | [[package]] 114 | name = "ark-serialize" 115 | version = "0.4.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" 118 | dependencies = [ 119 | "ark-serialize-derive", 120 | "ark-std", 121 | "digest", 122 | "num-bigint", 123 | ] 124 | 125 | [[package]] 126 | name = "ark-serialize-derive" 127 | version = "0.4.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" 130 | dependencies = [ 131 | "proc-macro2", 132 | "quote", 133 | "syn 1.0.109", 134 | ] 135 | 136 | [[package]] 137 | name = "ark-std" 138 | version = "0.4.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" 141 | dependencies = [ 142 | "num-traits", 143 | "rand", 144 | ] 145 | 146 | [[package]] 147 | name = "arrayref" 148 | version = "0.3.9" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" 151 | 152 | [[package]] 153 | name = "arrayvec" 154 | version = "0.7.6" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 157 | 158 | [[package]] 159 | name = "autocfg" 160 | version = "1.4.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 163 | 164 | [[package]] 165 | name = "blake3" 166 | version = "1.5.4" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" 169 | dependencies = [ 170 | "arrayref", 171 | "arrayvec", 172 | "cc", 173 | "cfg-if", 174 | "constant_time_eq", 175 | ] 176 | 177 | [[package]] 178 | name = "bumpalo" 179 | version = "3.16.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 182 | 183 | [[package]] 184 | name = "byteorder" 185 | version = "1.5.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 188 | 189 | [[package]] 190 | name = "cc" 191 | version = "1.1.28" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" 194 | dependencies = [ 195 | "shlex", 196 | ] 197 | 198 | [[package]] 199 | name = "cfg-if" 200 | version = "1.0.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 203 | 204 | [[package]] 205 | name = "constant_time_eq" 206 | version = "0.3.1" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" 209 | 210 | [[package]] 211 | name = "crypto-common" 212 | version = "0.1.6" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 215 | dependencies = [ 216 | "generic-array", 217 | "typenum", 218 | ] 219 | 220 | [[package]] 221 | name = "derivative" 222 | version = "2.2.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 225 | dependencies = [ 226 | "proc-macro2", 227 | "quote", 228 | "syn 1.0.109", 229 | ] 230 | 231 | [[package]] 232 | name = "digest" 233 | version = "0.10.7" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 236 | dependencies = [ 237 | "crypto-common", 238 | ] 239 | 240 | [[package]] 241 | name = "either" 242 | version = "1.13.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 245 | 246 | [[package]] 247 | name = "generic-array" 248 | version = "0.14.7" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 251 | dependencies = [ 252 | "typenum", 253 | "version_check", 254 | ] 255 | 256 | [[package]] 257 | name = "getrandom" 258 | version = "0.2.15" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 261 | dependencies = [ 262 | "cfg-if", 263 | "js-sys", 264 | "libc", 265 | "wasi", 266 | "wasm-bindgen", 267 | ] 268 | 269 | [[package]] 270 | name = "hashbrown" 271 | version = "0.13.2" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 274 | dependencies = [ 275 | "ahash", 276 | ] 277 | 278 | [[package]] 279 | name = "itertools" 280 | version = "0.10.5" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 283 | dependencies = [ 284 | "either", 285 | ] 286 | 287 | [[package]] 288 | name = "js-sys" 289 | version = "0.3.71" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" 292 | dependencies = [ 293 | "wasm-bindgen", 294 | ] 295 | 296 | [[package]] 297 | name = "keaki" 298 | version = "0.1.0" 299 | dependencies = [ 300 | "ark-bls12-381", 301 | "ark-bn254", 302 | "ark-ec", 303 | "ark-ff", 304 | "ark-poly", 305 | "ark-serialize", 306 | "ark-std", 307 | "blake3", 308 | "getrandom", 309 | "rand", 310 | "thiserror", 311 | ] 312 | 313 | [[package]] 314 | name = "libc" 315 | version = "0.2.159" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 318 | 319 | [[package]] 320 | name = "log" 321 | version = "0.4.22" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 324 | 325 | [[package]] 326 | name = "num-bigint" 327 | version = "0.4.6" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 330 | dependencies = [ 331 | "num-integer", 332 | "num-traits", 333 | ] 334 | 335 | [[package]] 336 | name = "num-integer" 337 | version = "0.1.46" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 340 | dependencies = [ 341 | "num-traits", 342 | ] 343 | 344 | [[package]] 345 | name = "num-traits" 346 | version = "0.2.19" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 349 | dependencies = [ 350 | "autocfg", 351 | ] 352 | 353 | [[package]] 354 | name = "once_cell" 355 | version = "1.20.2" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 358 | 359 | [[package]] 360 | name = "paste" 361 | version = "1.0.15" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 364 | 365 | [[package]] 366 | name = "ppv-lite86" 367 | version = "0.2.20" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 370 | dependencies = [ 371 | "zerocopy", 372 | ] 373 | 374 | [[package]] 375 | name = "proc-macro2" 376 | version = "1.0.87" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" 379 | dependencies = [ 380 | "unicode-ident", 381 | ] 382 | 383 | [[package]] 384 | name = "quote" 385 | version = "1.0.37" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 388 | dependencies = [ 389 | "proc-macro2", 390 | ] 391 | 392 | [[package]] 393 | name = "rand" 394 | version = "0.8.5" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 397 | dependencies = [ 398 | "rand_chacha", 399 | "rand_core", 400 | ] 401 | 402 | [[package]] 403 | name = "rand_chacha" 404 | version = "0.3.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 407 | dependencies = [ 408 | "ppv-lite86", 409 | "rand_core", 410 | ] 411 | 412 | [[package]] 413 | name = "rand_core" 414 | version = "0.6.4" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 417 | dependencies = [ 418 | "getrandom", 419 | ] 420 | 421 | [[package]] 422 | name = "rustc_version" 423 | version = "0.4.1" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 426 | dependencies = [ 427 | "semver", 428 | ] 429 | 430 | [[package]] 431 | name = "semver" 432 | version = "1.0.23" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 435 | 436 | [[package]] 437 | name = "shlex" 438 | version = "1.3.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 441 | 442 | [[package]] 443 | name = "syn" 444 | version = "1.0.109" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 447 | dependencies = [ 448 | "proc-macro2", 449 | "quote", 450 | "unicode-ident", 451 | ] 452 | 453 | [[package]] 454 | name = "syn" 455 | version = "2.0.79" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 458 | dependencies = [ 459 | "proc-macro2", 460 | "quote", 461 | "unicode-ident", 462 | ] 463 | 464 | [[package]] 465 | name = "thiserror" 466 | version = "1.0.64" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 469 | dependencies = [ 470 | "thiserror-impl", 471 | ] 472 | 473 | [[package]] 474 | name = "thiserror-impl" 475 | version = "1.0.64" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 478 | dependencies = [ 479 | "proc-macro2", 480 | "quote", 481 | "syn 2.0.79", 482 | ] 483 | 484 | [[package]] 485 | name = "typenum" 486 | version = "1.17.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 489 | 490 | [[package]] 491 | name = "unicode-ident" 492 | version = "1.0.13" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 495 | 496 | [[package]] 497 | name = "version_check" 498 | version = "0.9.5" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 501 | 502 | [[package]] 503 | name = "wasi" 504 | version = "0.11.0+wasi-snapshot-preview1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 507 | 508 | [[package]] 509 | name = "wasm-bindgen" 510 | version = "0.2.94" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" 513 | dependencies = [ 514 | "cfg-if", 515 | "once_cell", 516 | "wasm-bindgen-macro", 517 | ] 518 | 519 | [[package]] 520 | name = "wasm-bindgen-backend" 521 | version = "0.2.94" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" 524 | dependencies = [ 525 | "bumpalo", 526 | "log", 527 | "once_cell", 528 | "proc-macro2", 529 | "quote", 530 | "syn 2.0.79", 531 | "wasm-bindgen-shared", 532 | ] 533 | 534 | [[package]] 535 | name = "wasm-bindgen-macro" 536 | version = "0.2.94" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" 539 | dependencies = [ 540 | "quote", 541 | "wasm-bindgen-macro-support", 542 | ] 543 | 544 | [[package]] 545 | name = "wasm-bindgen-macro-support" 546 | version = "0.2.94" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" 549 | dependencies = [ 550 | "proc-macro2", 551 | "quote", 552 | "syn 2.0.79", 553 | "wasm-bindgen-backend", 554 | "wasm-bindgen-shared", 555 | ] 556 | 557 | [[package]] 558 | name = "wasm-bindgen-shared" 559 | version = "0.2.94" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" 562 | 563 | [[package]] 564 | name = "zerocopy" 565 | version = "0.7.35" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 568 | dependencies = [ 569 | "byteorder", 570 | "zerocopy-derive", 571 | ] 572 | 573 | [[package]] 574 | name = "zerocopy-derive" 575 | version = "0.7.35" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 578 | dependencies = [ 579 | "proc-macro2", 580 | "quote", 581 | "syn 2.0.79", 582 | ] 583 | 584 | [[package]] 585 | name = "zeroize" 586 | version = "1.8.1" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 589 | dependencies = [ 590 | "zeroize_derive", 591 | ] 592 | 593 | [[package]] 594 | name = "zeroize_derive" 595 | version = "1.4.2" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 598 | dependencies = [ 599 | "proc-macro2", 600 | "quote", 601 | "syn 2.0.79", 602 | ] 603 | -------------------------------------------------------------------------------- /src/kzg/ptau.rs: -------------------------------------------------------------------------------- 1 | //! # Trusted Setup File Parser 2 | //! 3 | //! This module provides the functionality to obtain the necessary data for initializing a KZG commitment scheme from a Snark JS trusted setup `ptau` file. 4 | 5 | use ark_ec::{pairing::Pairing, AffineRepr}; 6 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress}; 7 | use ark_std::{io::Cursor, vec::Vec}; 8 | use std::{fs::File, io::Read, path::PathBuf}; 9 | use thiserror::Error; 10 | 11 | /// Header file type. 12 | const FILE_TYPE: &[u8; 4] = b"ptau"; 13 | /// Number of sections. 14 | const N_SECTIONS: usize = 11; 15 | /// File metadata length in bytes. 16 | const METADATA_LEN: usize = 12; 17 | /// Section header length in bytes. 18 | const SECTION_HEADER_LEN: usize = 12; 19 | 20 | /// G1 and G2 powers of tau tuple type alias. 21 | pub type PowersOfTau = (Vec<::G1Affine>, Vec<::G2Affine>); 22 | 23 | /// Section ID 24 | #[repr(u8)] 25 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] 26 | pub enum SectionId { 27 | #[default] 28 | /// Section descriptions. 29 | Header = 1, 30 | /// Points [tau^i]_1 31 | TauG1 = 2, 32 | /// Points [tau^i]_2 33 | TauG2 = 3, 34 | /// Points [alpha * tau^i]_1 35 | AlphaTauG1 = 4, 36 | /// Points [beta * tau^i]_1 37 | BetaTauG1 = 5, 38 | /// Single point [beta * tau]_2 39 | BetaG2 = 6, 40 | /// Previous contributions. 41 | Contributions = 7, 42 | // Phase 2 related sections. 43 | /// Lagrange basis [tau^i]_1 44 | LagrangeG1 = 12, 45 | /// Lagrange basis [tau^i]_2 46 | LagrangeG2 = 13, 47 | /// Lagrange basis [alpha * tau^i]_1 48 | LagrangeAlphaTauG1 = 14, 49 | /// Lagrange basis [beta * tau^i]_1 50 | LagrangeBetaTauG1 = 15, 51 | } 52 | 53 | impl SectionId { 54 | /// Returns the section index for the section ID. 55 | pub fn section_index(&self) -> usize { 56 | match self { 57 | SectionId::Header => 0, 58 | SectionId::TauG1 => 1, 59 | SectionId::TauG2 => 2, 60 | SectionId::AlphaTauG1 => 3, 61 | SectionId::BetaTauG1 => 4, 62 | SectionId::BetaG2 => 5, 63 | SectionId::Contributions => 6, 64 | SectionId::LagrangeG1 => 7, 65 | SectionId::LagrangeG2 => 8, 66 | SectionId::LagrangeAlphaTauG1 => 9, 67 | SectionId::LagrangeBetaTauG1 => 10, 68 | } 69 | } 70 | } 71 | 72 | impl TryFrom for SectionId { 73 | type Error = SetupFileError; 74 | 75 | fn try_from(value: u8) -> Result { 76 | match value { 77 | 1 => Ok(SectionId::Header), 78 | 2 => Ok(SectionId::TauG1), 79 | 3 => Ok(SectionId::TauG2), 80 | 4 => Ok(SectionId::AlphaTauG1), 81 | 5 => Ok(SectionId::BetaTauG1), 82 | 6 => Ok(SectionId::BetaG2), 83 | 7 => Ok(SectionId::Contributions), 84 | 12 => Ok(SectionId::LagrangeG1), 85 | 13 => Ok(SectionId::LagrangeG2), 86 | 14 => Ok(SectionId::LagrangeAlphaTauG1), 87 | 15 => Ok(SectionId::LagrangeBetaTauG1), 88 | _ => Err(SetupFileError::UnknownSection(value)), 89 | } 90 | } 91 | } 92 | 93 | /// File Loader 94 | pub struct FileLoader { 95 | filepath: PathBuf, 96 | } 97 | 98 | impl FileLoader { 99 | /// Creates a new instance. 100 | pub fn new(filepath: PathBuf) -> Self { 101 | Self { filepath } 102 | } 103 | 104 | /// Returns the path to the file. 105 | pub fn filepath(&self) -> &PathBuf { 106 | &self.filepath 107 | } 108 | 109 | /// Loads the file. 110 | pub fn load(&self) -> Result, SetupFileError> { 111 | let mut file = 112 | File::open(&self.filepath).map_err(|e| SetupFileError::FileError(e.to_string()))?; 113 | 114 | let mut data = Vec::new(); 115 | file.read_to_end(&mut data) 116 | .map_err(|e| SetupFileError::FileError(e.to_string()))?; 117 | 118 | Ok(data) 119 | } 120 | } 121 | 122 | /// File Sections. 123 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] 124 | pub struct FileSections { 125 | /// Sections 126 | sections: [SectionInfo; N_SECTIONS], 127 | } 128 | 129 | impl FileSections { 130 | /// Parse the sections from the file data. 131 | pub fn parse(file_data: &[u8]) -> Result { 132 | let mut sections = [SectionInfo::default(); N_SECTIONS]; 133 | let mut offset = METADATA_LEN; 134 | 135 | for section in sections.iter_mut() { 136 | let mut section_header_data = [0u8; SECTION_HEADER_LEN]; 137 | section_header_data.copy_from_slice(&file_data[offset..offset + SECTION_HEADER_LEN]); 138 | 139 | *section = SectionInfo::new_from_data(section_header_data, offset)?; 140 | 141 | offset += SECTION_HEADER_LEN + section.size as usize; 142 | } 143 | 144 | Ok(Self { sections }) 145 | } 146 | 147 | /// Returns the section information. 148 | pub fn get(&self, id: SectionId) -> Result<&SectionInfo, SetupFileError> { 149 | self.sections 150 | .get(id.section_index()) 151 | .ok_or(SetupFileError::EmptySection(id as u8)) 152 | } 153 | } 154 | 155 | /// Section information. 156 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] 157 | pub struct SectionInfo { 158 | /// ID 159 | id: SectionId, 160 | /// Size 161 | size: u64, 162 | /// Position 163 | position: usize, 164 | } 165 | 166 | impl SectionInfo { 167 | /// Create a new instance 168 | pub fn new_from_data( 169 | data: [u8; SECTION_HEADER_LEN], 170 | offset: usize, 171 | ) -> Result { 172 | // The first four bytes are the section ID, but one is enough. 173 | let id = SectionId::try_from(data[0])?; 174 | 175 | let mut size = [0u8; 8]; 176 | size.copy_from_slice(&data[4..12]); 177 | let size: u64 = u64::from_le_bytes(size); 178 | 179 | let position = offset + SECTION_HEADER_LEN; 180 | 181 | Ok(Self { id, position, size }) 182 | } 183 | } 184 | 185 | /// Header Section 186 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 187 | pub struct HeaderSection { 188 | /// Curve Field Modulus 189 | field_modulus: Vec, 190 | /// Power of the current setup file 191 | power: u32, 192 | /// Full ceremony power 193 | ceremony_power: u32, 194 | } 195 | 196 | impl HeaderSection { 197 | pub fn parse(file_data: &[u8], sections: &FileSections) -> Result { 198 | let section_info = sections.get(SectionId::Header)?; 199 | let mut offset = section_info.position; 200 | 201 | // Get the byte length of the field modulus data 202 | let mut field_mod_size = [0u8; 4]; 203 | field_mod_size.copy_from_slice(&file_data[offset..offset + 4]); 204 | let field_mod_size = u32::from_le_bytes(field_mod_size); 205 | offset += 4; 206 | 207 | // Read the field modulus 208 | let field_modulus = file_data[offset..(offset + field_mod_size as usize)].to_vec(); 209 | offset += field_mod_size as usize; 210 | 211 | let mut power = [0u8; 4]; 212 | power.copy_from_slice(&file_data[offset..offset + 4]); 213 | let power = u32::from_le_bytes(power); 214 | offset += 4; 215 | 216 | let mut ceremony_power = [0u8; 4]; 217 | ceremony_power.copy_from_slice(&file_data[offset..offset + 4]); 218 | let ceremony_power = u32::from_le_bytes(ceremony_power); 219 | 220 | Ok(Self { 221 | field_modulus, 222 | power, 223 | ceremony_power, 224 | }) 225 | } 226 | } 227 | 228 | /// Powers of tau in G1 229 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 230 | pub struct TauG1Section { 231 | /// Powers of tau in G1 - [tau^i]_1 232 | powers: Vec, 233 | } 234 | 235 | impl TauG1Section { 236 | pub fn parse( 237 | file_data: &[u8], 238 | sections: &FileSections, 239 | power: u32, 240 | ) -> Result { 241 | let section_info = sections.get(SectionId::TauG1)?; 242 | 243 | // Number of elements 244 | let n_elements = (2u32.pow(power)) * 2 - 1; 245 | 246 | // Element size 247 | let element_size = E::G1Affine::generator().serialized_size(Compress::No); 248 | 249 | // Validate element size 250 | if section_info.size != element_size as u64 * n_elements as u64 { 251 | return Err(SetupFileError::ElementSizeMismatch( 252 | element_size as u64 * n_elements as u64, 253 | section_info.size, 254 | )); 255 | } 256 | 257 | // Deserialize 258 | let mut powers = Vec::new(); 259 | for chunk in file_data 260 | [section_info.position..section_info.position + section_info.size as usize] 261 | .chunks_exact(element_size) 262 | .take(n_elements as usize) 263 | { 264 | let mut reader = Cursor::new(chunk); 265 | 266 | let element = E::G1Affine::deserialize_uncompressed_unchecked(&mut reader) 267 | .map_err(|e| SetupFileError::ParseError(e.to_string()))?; 268 | 269 | powers.push(element); 270 | } 271 | 272 | Ok(Self { powers }) 273 | } 274 | } 275 | 276 | /// Powers of tau in G2 277 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 278 | pub struct TauG2Section { 279 | /// Powers of tau in G2 - [tau^i]_2 280 | powers: Vec, 281 | } 282 | 283 | impl TauG2Section { 284 | pub fn parse( 285 | file_data: &[u8], 286 | sections: &FileSections, 287 | power: u32, 288 | ) -> Result { 289 | let section_info = sections.get(SectionId::TauG2)?; 290 | 291 | // Number of elements 292 | let n_elements = 2u32.pow(power); 293 | 294 | // Element size 295 | let element_size = E::G2Affine::generator().serialized_size(Compress::No); 296 | 297 | // Validate element size 298 | if section_info.size != element_size as u64 * n_elements as u64 { 299 | return Err(SetupFileError::ElementSizeMismatch( 300 | element_size as u64 * n_elements as u64, 301 | section_info.size, 302 | )); 303 | } 304 | 305 | // Deserialize 306 | let mut powers = Vec::new(); 307 | for chunk in file_data 308 | [section_info.position..section_info.position + section_info.size as usize] 309 | .chunks_exact(element_size) 310 | .take(n_elements as usize) 311 | { 312 | let mut reader = Cursor::new(chunk); 313 | 314 | let element = E::G2Affine::deserialize_uncompressed_unchecked(&mut reader) 315 | .map_err(|e| SetupFileError::ParseError(e.to_string()))?; 316 | 317 | powers.push(element); 318 | } 319 | 320 | Ok(Self { powers }) 321 | } 322 | } 323 | 324 | /// Verifies file metadata. 325 | pub fn verify_metadata(file_data: &[u8]) -> Result<(), SetupFileError> { 326 | let mut file_type = [0u8; 4]; 327 | file_type.copy_from_slice(&file_data[0..4]); 328 | 329 | if file_type != *FILE_TYPE { 330 | return Err(SetupFileError::InvalidFileType(file_type)); 331 | } 332 | 333 | // Version number occupies bytes 4 through 8 334 | 335 | let mut n_sections = [0u8; 4]; 336 | n_sections.copy_from_slice(&file_data[8..12]); 337 | let n_sections = u32::from_le_bytes(n_sections); 338 | 339 | if n_sections != N_SECTIONS as u32 { 340 | return Err(SetupFileError::InvalidNumberOfSections(n_sections)); 341 | } 342 | 343 | Ok(()) 344 | } 345 | 346 | /// Returns the G1 and G2 powers of tau. 347 | pub fn get_powers_from_file(file: &str) -> Result, SetupFileError> { 348 | let file_data = FileLoader::new(PathBuf::from(file)).load()?; 349 | verify_metadata(&file_data)?; 350 | 351 | let sections = FileSections::parse(&file_data)?; 352 | 353 | let header_section = HeaderSection::parse(&file_data, §ions)?; 354 | let tau_g1_section = TauG1Section::::parse(&file_data, §ions, header_section.power)?; 355 | let tau_g2_section = TauG2Section::::parse(&file_data, §ions, header_section.power)?; 356 | 357 | Ok((tau_g1_section.powers, tau_g2_section.powers)) 358 | } 359 | 360 | #[derive(Error, Debug, PartialEq, Eq)] 361 | pub enum SetupFileError { 362 | #[error("Element size mismatch. Obtained: {0:?}, Expected: {1:?}")] 363 | ElementSizeMismatch(u64, u64), 364 | #[error("Section is uninitialized: {0}")] 365 | EmptySection(u8), 366 | #[error("File error: {0:?}")] 367 | FileError(String), 368 | #[error("Invalid file type: {0:?}")] 369 | InvalidFileType([u8; 4]), 370 | #[error("Invalid number of sections: {0:?}")] 371 | InvalidNumberOfSections(u32), 372 | #[error("IO error: {0}")] 373 | ParseError(String), 374 | #[error("Unknown section ID: {0}")] 375 | UnknownSection(u8), 376 | } 377 | 378 | #[cfg(test)] 379 | mod tests { 380 | use super::*; 381 | use ark_bn254::Bn254; 382 | use ark_ff::{BigInt, BigInteger, PrimeField}; 383 | 384 | pub const TEST_PTAU_FILEPATH: &str = "ptau/ppot_0080_01.ptau.test"; 385 | pub const TEST_FILE_LEN: usize = 95_634; 386 | 387 | pub const TEST_CEREMONY_POWER: u32 = 28; 388 | pub const TEST_FILE_POWER: u32 = 1; 389 | 390 | pub const BN254_FIELD_MOD: BigInt<4> = ::BaseField::MODULUS; 391 | 392 | #[test] 393 | fn test_section_id() { 394 | assert_eq!(SectionId::try_from(1), Ok(SectionId::Header)); 395 | assert_eq!(SectionId::try_from(2), Ok(SectionId::TauG1)); 396 | assert_eq!(SectionId::try_from(3), Ok(SectionId::TauG2)); 397 | assert!(SectionId::try_from(99).is_err()); 398 | } 399 | 400 | #[test] 401 | fn test_file_loader() { 402 | let loader = FileLoader::new(PathBuf::from(TEST_PTAU_FILEPATH)); 403 | let file_data = loader.load().expect("Failed to load the test ptau file"); 404 | 405 | assert_eq!(file_data.len(), TEST_FILE_LEN); 406 | } 407 | 408 | #[test] 409 | fn test_verify_metadata() { 410 | let loader = FileLoader::new(PathBuf::from(TEST_PTAU_FILEPATH)); 411 | let file_data = loader.load().expect("Failed to load the test ptau file"); 412 | 413 | assert_eq!(verify_metadata(&file_data), Ok(())); 414 | } 415 | 416 | #[test] 417 | fn test_section_info() { 418 | let test_section_data = [1, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0]; 419 | let section_info = SectionInfo::new_from_data(test_section_data, 0).unwrap(); 420 | 421 | assert_eq!(section_info.id, SectionId::Header); 422 | assert_eq!(section_info.size, 44); 423 | assert_eq!(section_info.position, SECTION_HEADER_LEN); 424 | } 425 | 426 | #[test] 427 | fn test_file_sections() { 428 | let loader = FileLoader::new(PathBuf::from(TEST_PTAU_FILEPATH)); 429 | let file_data = loader.load().unwrap(); 430 | let sections = FileSections::parse(&file_data).unwrap(); 431 | 432 | for i in 0..N_SECTIONS { 433 | let section = sections.sections[i]; 434 | 435 | // Assert the section index 436 | let expected_section_index = section.id.section_index(); 437 | assert_eq!(i, expected_section_index); 438 | 439 | assert!(section.size > 0); 440 | assert!(section.position + section.size as usize <= file_data.len()); 441 | } 442 | 443 | // Assert the section sizes and positions 444 | for i in 0..N_SECTIONS - 1 { 445 | let current_section = sections.sections[i]; 446 | let next_section = sections.sections[i + 1]; 447 | 448 | // For the header, the position should be METADATA_LEN + SECTION_HEADER_LEN 449 | if i == 0 { 450 | assert_eq!(current_section.position, METADATA_LEN + SECTION_HEADER_LEN); 451 | } 452 | 453 | assert_eq!( 454 | current_section.position + current_section.size as usize + SECTION_HEADER_LEN, 455 | next_section.position 456 | ); 457 | } 458 | } 459 | 460 | #[test] 461 | fn test_header_section() { 462 | let loader = FileLoader::new(PathBuf::from(TEST_PTAU_FILEPATH)); 463 | let file_data = loader.load().unwrap(); 464 | let sections = FileSections::parse(&file_data).unwrap(); 465 | 466 | let header_section = HeaderSection::parse(&file_data, §ions).unwrap(); 467 | 468 | assert_eq!( 469 | header_section.field_modulus, 470 | BN254_FIELD_MOD.to_bytes_le().to_vec() 471 | ); 472 | assert_eq!(header_section.power, TEST_FILE_POWER); 473 | assert_eq!(header_section.ceremony_power, TEST_CEREMONY_POWER); 474 | } 475 | 476 | #[test] 477 | fn test_tau_g1_section() { 478 | let loader = FileLoader::new(PathBuf::from(TEST_PTAU_FILEPATH)); 479 | let file_data = loader.load().unwrap(); 480 | let sections = FileSections::parse(&file_data).unwrap(); 481 | let header_section = HeaderSection::parse(&file_data, §ions).unwrap(); 482 | 483 | let tau_g1_section = 484 | TauG1Section::::parse(&file_data, §ions, header_section.power).unwrap(); 485 | 486 | assert_eq!( 487 | tau_g1_section.powers.len(), 488 | 2u32.pow(TEST_FILE_POWER) as usize * 2 - 1 489 | ); 490 | } 491 | 492 | #[test] 493 | fn test_tau_g2_section() { 494 | let loader = FileLoader::new(PathBuf::from(TEST_PTAU_FILEPATH)); 495 | let file_data = loader.load().unwrap(); 496 | let sections = FileSections::parse(&file_data).unwrap(); 497 | let header_section = HeaderSection::parse(&file_data, §ions).unwrap(); 498 | 499 | let tau_g2_section = 500 | TauG2Section::::parse(&file_data, §ions, header_section.power).unwrap(); 501 | 502 | assert_eq!( 503 | tau_g2_section.powers.len(), 504 | 2u32.pow(TEST_FILE_POWER) as usize 505 | ); 506 | } 507 | 508 | #[test] 509 | fn test_powers_of_tau() { 510 | let (g1_pow, g2_pow) = get_powers_from_file::(TEST_PTAU_FILEPATH).unwrap(); 511 | 512 | assert_eq!(g1_pow.len(), 2u32.pow(TEST_FILE_POWER) as usize * 2 - 1); 513 | assert_eq!(g2_pow.len(), 2u32.pow(TEST_FILE_POWER) as usize); 514 | } 515 | } 516 | --------------------------------------------------------------------------------