wip will modify circuit.circom to not chain hashes

This commit is contained in:
themighty1
2022-08-03 07:27:21 +03:00
parent f1a6f92f07
commit de09f4daaf
5 changed files with 243 additions and 30 deletions

View File

@@ -1,10 +1,14 @@
[package]
name = "label_decoding"
name = "label_sum"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "label_sum"
[dependencies]
num = { version = "0.4"}
rand = "0.8.5"
json = "0.12.4"

5
README
View File

@@ -4,13 +4,16 @@ Install snarkjs https://github.com/iden3/snarkjs
Download powers of tau^14 https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_14.ptau
Run:
python3 script.py 10
python3 script.py 16
# 10 is how much plaintext (in Field elements of ~32 bytes) we want
# to decode inside the snark. (For tau^14 max is 21)
# if you need more than 21, you'll need to download another ptau file from
# https://github.com/iden3/snarkjs#7-prepare-phase-2
circom circuit.circom --r1cs --wasm
snarkjs groth16 setup circuit.r1cs powersOfTau28_hez_final_14.ptau circuit_0000.zkey
snarkjs groth16 setup circuit.r1cs pot14_bls12_final.ptau circuit_0000.zkey
snarkjs zkey contribute circuit_0000.zkey circuit_final.zkey -v -e="Notary's entropy"
snarkjs zkey export verificationkey circuit_final.zkey verification_key.json
snarkjs groth16 fullprove input.json circuit_js/circuit.wasm circuit_final.zkey proof.json public.json

86
src/lib.rs Normal file
View File

@@ -0,0 +1,86 @@
pub mod prover;
pub mod verifier;
const BN254_PRIME: &str =
"21888242871839275222246405745257275088548364400416034343698204186575808495617";
#[cfg(test)]
mod tests {
use crate::prover::{boolvec_to_u8vec, u8vec_to_boolvec};
use super::*;
use num::{BigUint, FromPrimitive};
use prover::LsumProver;
use rand::{thread_rng, Rng};
use verifier::LsumVerifier;
fn random_bigint(bitsize: usize) -> BigUint {
assert!(bitsize <= 128);
let r: [u8; 16] = thread_rng().gen();
// take only those bits which we need
BigUint::from_bytes_be(&boolvec_to_u8vec(&u8vec_to_boolvec(&r)[0..bitsize]))
}
/// Unzips a slice of pairs, returning items corresponding to choice
fn choose<T: Clone>(items: &[[T; 2]], choice: &[bool]) -> Vec<T> {
assert!(items.len() == choice.len(), "arrays are different length");
items
.iter()
.zip(choice)
.map(|(items, choice)| items[*choice as usize].clone())
.collect()
}
#[test]
fn test() {
let mut rng = thread_rng();
// random 2KB plaintext
let mut plaintext = [0u8; 1024];
rng.fill(&mut plaintext);
// bn254 prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
// in decimal 21888242871839275222246405745257275088548364400416034343698204186575808495617
// TODO: this will be done internally by verifier. But for now, until b2a converter is
// implemented, we do it here.
// generate as many 128-bit arithm label pairs as there are plaintext bits.
// The 128-bit size is for convenience to be able to encrypt the label with 1
// call to AES.
// To keep the handling simple, we want to avoid a negative delta, that's why
// W_0 and delta must be a 127-bit value and W_1 will be set to W_0 + delta
let bitsize = plaintext.len() * 8;
let mut zero_sum = BigUint::from_u8(0).unwrap();
let mut deltas: Vec<BigUint> = Vec::with_capacity(bitsize);
let arithm_labels: Vec<[BigUint; 2]> = (0..bitsize)
.map(|_| {
let zero_label = random_bigint(127);
let delta = random_bigint(127);
let one_label = zero_label.clone() + delta.clone();
zero_sum += zero_label.clone();
deltas.push(delta);
[zero_label, one_label]
})
.collect();
let prime = String::from(BN254_PRIME).parse::<BigUint>().unwrap();
let mut prover = LsumProver::new(plaintext.to_vec(), prime);
let plaintext_comm = prover.setup();
// Commitment to the plaintext is sent to the Notary
let mut verifier = LsumVerifier::new();
verifier.receive_pt_hashes(plaintext_comm);
// Verifier sends back encrypted arithm. labels. We skip this step
// and simulate Prover's deriving his arithm labels:
let prover_labels = choose(&arithm_labels, &u8vec_to_boolvec(&plaintext));
let mut label_sum = BigUint::from_u8(0).unwrap();
for i in 0..prover_labels.len() {
label_sum += prover_labels[i].clone();
}
// Prover sends a hash commitment to label_sum
let label_sum_hash = prover.poseidon(vec![label_sum.clone()]);
// Commitment to the label_sum is sent to the Notary
verifier.receive_labelsum_hash(label_sum_hash);
// Notary sends zero_sum and all deltas
// Prover constructs input to snarkjs
prover.create_zk_proof(zero_sum, deltas, label_sum);
}
}

View File

@@ -1,7 +1,13 @@
use json::{object, stringify, stringify_pretty};
/// implementing the "label sum" protocol
///
use num::{BigUint, FromPrimitive, ToPrimitive, Zero};
use rand::{thread_rng, Rng};
use std::fs;
use std::process::Command;
use std::str;
use super::BN254_PRIME;
// implementation of the Prover in the "label sum" protocol (aka the User).
pub struct LsumProver {
@@ -9,7 +15,7 @@ pub struct LsumProver {
// the prime of the field in which Poseidon hash will be computed.
field_prime: BigUint,
// how many bits to pack into one field element
bits_to_pack: Option<usize>,
useful_bits: Option<usize>,
// We will compute a separate Poseidon hash on each chunk of the plaintext.
// Each chunk contains 16 field elements.
chunks: Option<Vec<[BigUint; 16]>>,
@@ -30,18 +36,20 @@ impl LsumProver {
Self {
plaintext,
field_prime,
bits_to_pack: None,
useful_bits: None,
chunks: None,
salts: None,
}
}
pub fn setup(&mut self) {
let bits_to_pack = compute_full_bits(self.field_prime.clone());
self.bits_to_pack = Some(bits_to_pack);
// Return hash digests which is Prover's commitment to the plaintext
pub fn setup(&mut self) -> Vec<BigUint> {
let useful_bits = compute_useful_bits(self.field_prime.clone());
self.useful_bits = Some(useful_bits);
let (chunks, salts) = self.plaintext_to_chunks();
self.chunks = Some(chunks);
self.chunks = Some(chunks.clone());
self.salts = Some(salts);
self.hash_chunks(chunks)
}
// create chunks of plaintext where each chunk consists of 16 field elements.
@@ -49,9 +57,11 @@ impl LsumProver {
// If there is not enough plaintext to fill the whole chunk, we fill the gap
// with zero bits.
fn plaintext_to_chunks(&mut self) -> (Vec<[BigUint; 16]>, Vec<BigUint>) {
let bits_to_pack = self.bits_to_pack.unwrap();
let useful_bits = self.useful_bits.unwrap();
// the size of a chunk of plaintext not counting the salt
let chunk_size = bits_to_pack * 16 - 128;
// let chunk_size = useful_bits * 16 - 128;
// TODO dont use the salt for now
let chunk_size = useful_bits * 16;
// plaintext converted into bits
let mut bits = u8vec_to_boolvec(&self.plaintext);
// chunk count (rounded up)
@@ -83,42 +93,101 @@ impl LsumProver {
BigUint::default(),
BigUint::default(),
];
for j in 0..15 {
// TODO dont use salt for now, to make debugging easier, later change this to
// for j in 0..15 { and uncomment the lines below //offset and //chunk[15]
for j in 0..16 {
// convert bits into field element
chunk[j] =
BigUint::from_bytes_be(&boolvec_to_u8vec(&bits[offset..offset + bits_to_pack]));
offset += bits_to_pack;
BigUint::from_bytes_be(&boolvec_to_u8vec(&bits[offset..offset + useful_bits]));
offset += useful_bits;
}
// last field element's last 128 bits are for the salt
let mut rng = thread_rng();
let salt: [u8; 16] = rng.gen();
let salt = u8vec_to_boolvec(&salt);
// let mut rng = thread_rng();
// let salt: [u8; 16] = rng.gen();
// let salt = u8vec_to_boolvec(&salt);
let mut last_fe = vec![false; bits_to_pack];
last_fe[0..bits_to_pack - 128]
.copy_from_slice(&bits[offset..offset + (bits_to_pack - 128)]);
offset += bits_to_pack - 128;
last_fe[bits_to_pack - 128..].copy_from_slice(&salt);
chunk[15] = BigUint::from_bytes_be(&boolvec_to_u8vec(&last_fe));
// let mut last_fe = vec![false; useful_bits];
// last_fe[0..useful_bits - 128]
// .copy_from_slice(&bits[offset..offset + (useful_bits - 128)]);
//offset += useful_bits - 128;
// last_fe[useful_bits - 128..].copy_from_slice(&salt);
//chunk[15] = BigUint::from_bytes_be(&boolvec_to_u8vec(&last_fe));
let salt = BigUint::from_bytes_be(&boolvec_to_u8vec(&salt));
salts.push(salt);
// let salt = BigUint::from_bytes_be(&boolvec_to_u8vec(&salt));
// salts.push(salt);
chunks.push(chunk);
}
(chunks, salts)
}
// hashes each chunk with Poseidon and returns digests for each chunk
fn hash_chunks(&mut self, chunks: Vec<[BigUint; 16]>) -> Vec<BigUint> {
return chunks
.iter()
.map(|chunk| self.poseidon(chunk.to_vec()))
.collect();
}
// hash the inputs with Poseidon with circomlibjs
pub fn poseidon(&mut self, inputs: Vec<BigUint>) -> BigUint {
// escape every field elements
let strchunks: Vec<String> = inputs
.iter()
.map(|fe| String::from("\"") + &fe.to_string() + &String::from("\""))
.collect();
// convert to JSON array
let json = String::from("[") + &strchunks.join(", ") + &String::from("]");
println!("strchunks {:?}", json);
let output = Command::new("node")
.args(["poseidon.mjs", &json])
.output()
.unwrap();
// drop the trailing new line
let output = &output.stdout[0..output.stdout.len() - 1];
let s = String::from_utf8(output.to_vec()).unwrap();
let bi = s.parse::<BigUint>().unwrap();
println!("poseidon output {:?}", bi);
bi
}
pub fn create_zk_proof(&mut self, zero_sum: BigUint, deltas: Vec<BigUint>, label_sum: BigUint) {
// write inputs into input.json
let pt_str: Vec<String> = self.chunks.as_ref().unwrap()[0]
.to_vec()
.iter()
.map(|bigint| bigint.to_string())
.collect();
// There are as many deltas as there are bits in the plaintext
let delta_str: Vec<String> = deltas[0..self.useful_bits.unwrap() * 16]
.iter()
.map(|bigint| bigint.to_string())
.collect();
let mut data = object! {
sum_of_labels: label_sum.to_string(),
sum_of_zero_labels: zero_sum.to_string(),
plaintext: pt_str,
delta: delta_str
};
let s = stringify_pretty(data, 4);
fs::write("input2.json", s).expect("Unable to write file");
}
}
/// Computes how many bits of plaintext we will pack into one field element.
/// Essentially, this is field_prime bit length minus 1.
fn compute_full_bits(field_prime: BigUint) -> usize {
fn compute_useful_bits(field_prime: BigUint) -> usize {
(field_prime.bits() - 1) as usize
}
#[test]
fn test_compute_full_bits() {
assert_eq!(compute_full_bits(BigUint::from_u16(13).unwrap()), 3);
assert_eq!(compute_full_bits(BigUint::from_u16(255).unwrap()), 7);
assert_eq!(compute_useful_bits(BigUint::from_u16(13).unwrap()), 3);
assert_eq!(compute_useful_bits(BigUint::from_u16(255).unwrap()), 7);
assert_eq!(
compute_useful_bits(String::from(BN254_PRIME,).parse::<BigUint>().unwrap()),
253
);
}
#[inline]
@@ -200,8 +269,31 @@ mod tests {
// salt has been set, i.e. it is not equal 0
assert!(!prover.chunks.clone().unwrap()[1][15].eq(&BigUint::from_u8(0).unwrap()));
}
}
fn main() {
println!("Hello, world!");
#[test]
fn test_hash_chunks() {
// 137-bit prime. Plaintext will be packed into 136 bits (17 bytes).
let mut prime = vec![false; 137];
prime[0] = true;
let prime = boolvec_to_u8vec(&prime);
let prime = BigUint::from_bytes_be(&prime);
// plaintext will spawn 2 chunks
let mut plaintext = vec![0u8; 17 * 15 + 1 + 17 * 5];
let mut prover = LsumProver::new(plaintext, prime);
//LsumProver::hash_chunks(&mut prover);
}
#[test]
fn test_json() {
let mut prime = vec![false; 137];
prime[0] = true;
let prime = boolvec_to_u8vec(&prime);
let prime = BigUint::from_bytes_be(&prime);
let v = vec![BigUint::from_u8(0).unwrap(), BigUint::from_u8(1).unwrap()];
let v_str: Vec<String> = v.iter().map(|bigint| bigint.to_string()).collect();
let mut data = object! {
foo:v_str
};
println!("{:?}", data.dump());
}
}

28
src/verifier.rs Normal file
View File

@@ -0,0 +1,28 @@
use num::{BigUint, FromPrimitive, ToPrimitive, Zero};
// implementation of the Verifier in the "label sum" protocol (aka the Notary).
pub struct LsumVerifier {
// hashes for each chunk of Prover's plaintext
plaintext_hashes: Option<Vec<BigUint>>,
labelsum_hash: Option<BigUint>,
}
impl LsumVerifier {
pub fn new() -> Self {
Self {
plaintext_hashes: None,
labelsum_hash: None,
}
}
// receive hashes of plaintext and reveal the arithmetic labels
pub fn receive_pt_hashes(&mut self, hashes: Vec<BigUint>) {
self.plaintext_hashes = Some(hashes);
// TODO at this stage we send 2 ciphertexts (encrypted arithm. labels),
// only 1 of which the User can decrypt
}
pub fn receive_labelsum_hash(&mut self, hash: BigUint) {
self.labelsum_hash = Some(hash);
}
}