mirror of
https://github.com/tlsnotary/label_decoding.git
synced 2026-01-09 20:18:06 -05:00
wip will modify circuit.circom to not chain hashes
This commit is contained in:
@@ -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
5
README
@@ -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
86
src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
28
src/verifier.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user