Skip to content

Commit

Permalink
Merge pull request #29 from tchataigner/feature/keccak-example
Browse files Browse the repository at this point in the history
Introducing Keccak example and cleanup
  • Loading branch information
tchataigner authored Jan 15, 2024
2 parents 77e96cd + 0fda740 commit d526f72
Show file tree
Hide file tree
Showing 17 changed files with 1,192 additions and 500 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/Cargo.lock

.vscode/
.idea/
7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ rust-version="1.70.0"

[dependencies]
anyhow = "1.0.65"
bellpepper-core = "0.4.0"
bellpepper-core = { git = "https://github.com/lurk-lab/bellpepper", branch = "dev" }
byteorder = "1.4.3"
cfg-if = "1.0.0"
color-eyre = "0.6.2"
crypto-bigint = { version = "0.5.2", features = ["serde"] }
ff = { version = "0.13", features = ["derive"] }
fnv = "1.0.7"
itertools = "0.9.0"
log = { version = "0.4.20", features = [] }
serde = "1.0"
serde_json = "1.0.85"
thiserror = "1.0.43"
Expand All @@ -39,6 +39,5 @@ getrandom = { version = "0.2.10", features = ["js"] }
pasta_curves = { version = "0.5.1" }

[features]
default = ["circom-2"]
circom-2 = []
default = []
llvm = ["dep:wasmer-compiler-llvm"]
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

This repository provides necessary middleware to take generated output of the Circom compiler (R1CS constraints and generated witnesses) and use them with Bellperson. It is based off the work of [Nova-Scotia](https://github.com/nalinbhardwaj/Nova-Scotia) and Arkworks' [Circom-Compat](https://github.com/arkworks-rs/circom-compat). Please see **Credits** at the bottom for proper credits towards the various works used here.

> ⚠️ Note: `circom-scotia` only provide support for Circom 2.*
## How?

To use it yourself, install version 2.1.6 or greater of [Circom](https://docs.circom.io). Refer to the [Circom documentation](https://docs.circom.io/getting-started/installation/#installing-dependencies) for more information.
Expand Down Expand Up @@ -72,5 +74,10 @@ For the full code, see the [`sha256.rs`](https://github.com/lurk-lab/circom-scot

Credits to the [Circom language](https://github.com/iden3/circom) from the iden3 team.

The parsing and generation borrows judiciously from [Nova-Scotia](https://github.com/nalinbhardwaj/Nova-Scotia) and [ark-circom](https://github.com/gakonst/ark-circom), respectively. All the loading code is essentially copied over. The `wasmer` witness generator was copied, then retrofitted for support without `arkworks` libraries such as `ark-ff` or `ark-bignum`; these were replaced with `ff` and `crypto-bignum`. The other bits that glue everything together is original.
The parsing and generation borrows judiciously from [Nova-Scotia](https://github.com/nalinbhardwaj/Nova-Scotia) and [ark-circom](https://github.com/gakonst/ark-circom), respectively. All the
loading code is essentially copied over. The `wasmer` witness generator was copied, then retrofitted for support without
`arkworks` libraries such as `ark-ff` or `ark-bignum`; these were replaced with `ff` and `crypto-bignum`. The other bits
that glue everything together is original.

Special thanks to Hanting Zhang (@winston-h-zhang) for porting the original code and retrofitting it.

89 changes: 89 additions & 0 deletions examples/keccak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#![allow(clippy::expect_used, clippy::unwrap_used)]

use bellpepper_core::ConstraintSystem;
use circom_scotia::{calculate_witness, r1cs::CircomConfig, synthesize};

use pasta_curves::vesta::Base as Fr;
use std::env::current_dir;

use bellpepper_core::test_cs::TestConstraintSystem;
use circom_scotia::r1cs::CircomInput;
use pasta_curves::Fq;

fn main() {
let root = current_dir().unwrap().join("examples/keccak");
let wtns = root.join("circom_keccak256.wasm");
let r1cs = root.join("circom_keccak256.r1cs");

let mut cs = TestConstraintSystem::<Fr>::new();
let cfg = CircomConfig::new(wtns, r1cs).unwrap();

let input_bytes = [
116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
];
let expected_output = [
37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, 212,
4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21,
];

let input_bits = bytes_to_bits(&input_bytes);

let arg_in = CircomInput {
name: "in".into(),
value: input_bits.clone().iter().map(|b| Fr::from(*b)).collect(),
};
let input = vec![arg_in];
let witness = calculate_witness(&cfg, input, true).expect("msg");

let output = synthesize(
&mut cs.namespace(|| "keccak_circom"),
cfg.r1cs.clone(),
Some(witness),
);

let state_out_fq = output.unwrap();
let state_out_bits: Vec<bool> = state_out_fq
.iter()
.map(|an| Fq::one() == an.get_value().unwrap())
.collect();
let state_out_bytes = bits_to_bytes(&state_out_bits);

assert_eq!(state_out_bytes, expected_output);
}

// Transforms a slice of bits in a slice of bytes. Fills the bytes from least to most significant
// bit.
fn bits_to_bytes(bits: &[bool]) -> Vec<u8> {
let mut bytes = vec![0; (bits.len() + 7) / 8]; // Initialize a vector with zeroes

for (i, &bit) in bits.iter().enumerate() {
// Iterate over each bit with its index
let byte_index = i / 8; // Calculate the byte index for the current bit
if bit {
// If the current bit is true,
bytes[byte_index] |= 1 << (i % 8); // Set the corresponding bit in the byte
}
}
bytes // Return the array of bytes
}

// Transforms a slice of bytes to a slice of bits. When dividing one byte in bits, order the bits
// from the least significant to the most significant one.
fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
let mut bits = Vec::new(); // Create a new, empty vector to store bits

for &byte in bytes.iter() {
// Iterate over each byte in the input slice
for j in 0..8 {
// For each bit in the byte
if byte & (1 << j) > 0 {
// Check if the bit is set
bits.push(true); // If the bit is set, push 1 to the vector
} else {
bits.push(false); // If the bit is not set, push 0
}
}
}
bits // Return the vector of bits
}
Binary file added examples/keccak/circom_keccak256.r1cs
Binary file not shown.
Binary file added examples/keccak/circom_keccak256.wasm
Binary file not shown.
10 changes: 8 additions & 2 deletions examples/sha256.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::expect_used, clippy::unwrap_used)]

use bellpepper_core::ConstraintSystem;
use circom_scotia::{calculate_witness, r1cs::CircomConfig, synthesize};
use ff::Field;
Expand All @@ -7,6 +9,7 @@ use std::env::current_dir;

use bellpepper_core::test_cs::TestConstraintSystem;
use bellpepper_core::Comparable;
use circom_scotia::r1cs::CircomInput;

fn main() {
let root = current_dir().unwrap().join("examples/sha256");
Expand All @@ -16,7 +19,10 @@ fn main() {
let mut cs = TestConstraintSystem::<Fr>::new();
let cfg = CircomConfig::new(wtns, r1cs).unwrap();

let arg_in = ("arg_in".into(), vec![Fr::ZERO, Fr::ZERO]);
let arg_in = CircomInput {
name: "arg_in".into(),
value: vec![Fr::ZERO, Fr::ZERO],
};
let input = vec![arg_in];
let witness = calculate_witness(&cfg, input, true).expect("msg");

Expand All @@ -27,7 +33,7 @@ fn main() {
);

let expected = "0x00000000008619b3767c057fdf8e6d99fde2680c5d8517eb06761c0878d40c40";
let output_num = format!("{:?}", output.unwrap().get_value().unwrap());
let output_num = format!("{:?}", output.unwrap()[0].get_value().unwrap());
assert!(output_num == expected);

assert!(cs.is_satisfied());
Expand Down
5 changes: 0 additions & 5 deletions src/circom.rs

This file was deleted.

133 changes: 133 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use thiserror::Error;

/// Enum related to error happening while reading data from source.
#[derive(Error, Debug)]
pub enum ReaderError {
/// Error if we failed to open the file we want to read.
#[error("Failed to open file \"{filename}\": {source}")]
OpenFileError {
filename: String,
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// High level error returned if we could not read our .bin or .json file.
#[error("Failed to read witness from file \"{filename}\": {source}")]
ReadWitnessError {
filename: String,
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error thrown if the specified filename contains non-unicode characters.
#[error("Could not read provided file path. It most likely contains non-Unicode data.")]
FilenameError,
/// Error if we could not find the magic header 'wtns' in the witness file.
#[error("'witns' header not found.")]
WitnessHeaderError,
/// Error if we could not find the magic header 'r1cs' in the r1cs file.
#[error("'r1cs' header not found.")]
R1CSHeaderError,
/// Error thrown while failing to seek a new position in our buffer.
#[error("Error while seeking in buffer: {source}")]
SeekError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error thrown when we try to read a witness file with a non-supported version.
#[error("Witness version not supported. Version supported is 2.*, found {0}")]
WitnessVersionNotSupported(String),
/// Error thrown when we try to read a r1cs file with a non-supported version.
#[error("R1CS version not supported. Version supported is 1, found {0}")]
R1CSVersionNotSupported(String),
/// Error thrown when we try to read a section from our file and it does not exist.
#[error("Failed to find section {0}")]
SectionNotFound(String),
/// Error if the number of sections in the witness file is not two.
#[error("Invalid number of sections found in witness data. Expected 2 got {0}")]
SectionCountError(String),
/// Error thrown if the section we are reading is not of the type we expected.
#[error("Invalid section type. Expected {0}, got {1}")]
SectionTypeError(String, String),
/// Error thrown if the section we are reading is not of the length we expected.
#[error("Invalid section length. Expected {0}, got {1}")]
SectionLengthError(String, String),
/// Error thrown if the field we are reading is not of the size we expected.
#[error("Invalid field byte size. Expected {0}, got {1}")]
FieldByteSizeError(String, String),
/// Error if we tried to read an integer from the bytes and it failed.
#[error("Failed to read integer from bytes: {source}")]
ReadIntegerError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error if we tried to read a specified amount of bytes and it failed.
#[error("Failed to read bytes: {source}")]
ReadBytesError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error if we tried to read a field element from the bytes and it failed.
#[error("Failed to read field from bytes: {source}")]
ReadFieldError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error thrown if the specified modulus in the r1cs header is not the one we were expecting.
#[error("Mismatched prime field. Expected {expected}, read {value} in the header instead.")]
NonMatchingPrime { expected: String, value: String },
/// Error thrown when parsing wires in an R1CS file. We expect the first wire to always be mapped to 0.
#[error("Wire 0 should always be mapped to 0")]
WireError,
}

/// Enum related to witness generatiuon problems.
#[derive(Error, Debug)]
pub enum WitnessError {
/// Error if we could not execute the node command to generate our witness.
#[error("Failed to execute the witness generation: {source}")]
FailedExecutionError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error if we could not read the witness from the generated file.
#[error("Could not load witness from its generated file: {source}")]
LoadWitnessError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error generated while trying to access or alter the file system.
#[error("Could not interact with the file system: {source}")]
FileSystemError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error generated if a panic occurs when trying to access the content of our Mutex.
#[error("Could not acquire the witness calculator mutex lock.")]
MutexError,
/// Error if we could not calculate the witness.
#[error("Failed to calculate the witness: {source}")]
WitnessCalculationError {
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
}

/// Error related to the Circom configuration
#[derive(Error, Debug)]
pub enum CircomConfigError {
/// Error if we could not instantiate our Witness Calculator.
#[error(
"Could instantiate a witness calculator based on the witness file \"{path}\": {source}"
)]
WitnessCalculatorInstantiationError {
path: String,
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
/// Error if we could not load data from our R1CS file.
#[error("Could load r1cs data from the given file \"{path}\": {source}")]
LoadR1CSError {
path: String,
#[source]
source: Box<dyn std::error::Error + Sync + Send>,
},
}
Loading

0 comments on commit d526f72

Please sign in to comment.