#[cfg(test)]
use std::time::Instant;
use std::{
env,
fs::File,
io::{BufReader, Read, Write},
marker::PhantomData,
path::Path,
};
use midnight_curves::Bls12;
use midnight_proofs::{
plonk::{
create_proof, keygen_pk, keygen_vk, prepare, Circuit, Error, ProvingKey, VerifyingKey,
},
poly::{
commitment::Guard,
kzg::{
params::{ParamsKZG, ParamsVerifierKZG},
KZGCommitmentScheme,
},
},
transcript::{CircuitTranscript, Hashable, Sampleable, Transcript, TranscriptHash},
utils::SerdeFormat,
};
use rand::{CryptoRng, RngCore};
use sha2::Digest;
use crate::{cost_model, MidnightVK, Relation};
macro_rules! plonk_api {
($name:ident, $engine:ty, $native:ty, $curve:ty, $projective:ty) => {
#[derive(Debug)]
pub struct $name<Relation> {
_marker: PhantomData<Relation>,
}
impl<Relation> $name<Relation>
where
Relation: Circuit<$native>,
{
pub fn setup_vk(
params: &ParamsKZG<$engine>,
circuit: &Relation,
) -> VerifyingKey<$native, KZGCommitmentScheme<$engine>> {
#[cfg(test)]
let start = Instant::now();
let vk = keygen_vk(params, circuit).expect("keygen_vk should not fail");
#[cfg(test)]
println!("Generated vk in {} ms", start.elapsed().as_millis());
vk
}
pub fn setup_pk(
circuit: &Relation,
vk: &VerifyingKey<$native, KZGCommitmentScheme<$engine>>,
) -> ProvingKey<$native, KZGCommitmentScheme<$engine>> {
#[cfg(test)]
let start = Instant::now();
let pk = keygen_pk(vk.clone(), circuit).expect("keygen_pk should not fail");
#[cfg(test)]
println!("Generated pk in {} ms", start.elapsed().as_millis());
pk
}
pub fn prove<H>(
params: &ParamsKZG<$engine>,
pk: &ProvingKey<$native, KZGCommitmentScheme<$engine>>,
circuit: &Relation,
nb_instance_commitments: usize,
pi: &[&[$native]],
rng: impl RngCore + CryptoRng,
) -> Result<Vec<u8>, Error>
where
H: TranscriptHash,
$projective: Hashable<H>,
$native: Hashable<H> + Sampleable<H>,
{
#[cfg(test)]
let start = Instant::now();
let proof = {
let mut transcript = CircuitTranscript::init();
create_proof::<
$native,
KZGCommitmentScheme<$engine>,
CircuitTranscript<H>,
Relation,
>(
params,
pk,
std::slice::from_ref(circuit),
nb_instance_commitments,
&[pi],
&mut transcript,
rng,
)?;
transcript.finalize()
};
#[cfg(test)]
{
println!("Generated proof in {:?} ms", start.elapsed().as_millis());
println!("Proof size: {:?} bytes.", proof.len())
};
Ok(proof)
}
pub fn verify<H>(
params_verifier: &ParamsVerifierKZG<$engine>,
vk: &VerifyingKey<$native, KZGCommitmentScheme<$engine>>,
instance_commitments: &[$curve],
pi: &[&[$native]],
proof: &[u8],
) -> Result<(), Error>
where
H: TranscriptHash,
$projective: Hashable<H>,
$native: Hashable<H> + Sampleable<H>,
{
let mut transcript = CircuitTranscript::init_from_bytes(proof);
#[cfg(test)]
let start = Instant::now();
let res = prepare::<$native, KZGCommitmentScheme<$engine>, CircuitTranscript<H>>(
vk,
&[&instance_commitments.iter().map(|c| c.into()).collect::<Vec<_>>()],
&[pi],
&mut transcript,
)?;
transcript.assert_empty().map_err(|_| Error::Opening)?;
let res = res.verify(params_verifier);
#[cfg(test)]
println!("Proof verified in {:?} us", start.elapsed().as_micros());
res.map_err(|_| Error::Opening)
}
}
};
}
plonk_api!(
BlstPLONK,
midnight_curves::Bls12,
midnight_curves::Fq,
midnight_curves::G1Affine,
midnight_curves::G1Projective
);
pub fn check_vk<Relation: Circuit<midnight_curves::Fq>>(vk: &MidnightVK) {
let circuit_name = std::any::type_name::<Relation>()
.split("::")
.last()
.unwrap()
.split('>')
.next()
.unwrap();
let vk_name = format!("./tests/static_vks/{}Vk", circuit_name);
let mut vk_buffer: Vec<u8> = Vec::new();
vk.write(&mut vk_buffer, SerdeFormat::RawBytes).unwrap();
let vk_hash: [u8; 32] = sha2::Sha256::digest(&vk_buffer).into();
let vk_path = Path::new(&vk_name);
let error_msg = "The VK does not exist. This means that you are adding new functionality to midnight_lib. Make sure to update the CHANGELOG. To create the vk, re-run the example with env var CHANGE_VK=MINOR";
if File::open(vk_path).is_err() {
match std::env::var("CHANGE_VK") {
Ok(value) => {
if value == "MINOR" {
let mut file = File::create(vk_path).expect("Failed to create file");
file.write_all(&vk_hash).expect("Failed to write transcript hash to file");
} else {
panic!("{}", error_msg)
}
}
_ => panic!("{}", error_msg),
}
}
let mut vk_fs = File::open(vk_path).expect("couldn't load proof parameters");
let mut read_vk_hash = Vec::new();
vk_fs.read_to_end(&mut read_vk_hash).expect("Failed to read VK hash");
let read_vk_hash: [u8; 32] = read_vk_hash
.try_into()
.expect("The serialized VK is expected to contain 32 bytes");
let error_msg = "The VK does not match. This means that you are changing functionality from midnight_lib. Make sure to update the CHANGELOG with breaking changes. To create the vk, re-run the example with env var CHANGE_VK=BREAKING";
if vk_hash != read_vk_hash {
match std::env::var("CHANGE_VK") {
Ok(var) => {
if var == "BREAKING" {
let mut file = File::create(vk_path).expect("Failed to create file");
file.write_all(&vk_hash).expect("Failed to write transcript hash to file");
} else {
panic!("{}", error_msg)
}
}
_ => panic!("{}", error_msg),
}
}
}
pub fn update_circuit_goldenfiles<R: Relation>(relation: &R) {
let circuit_name = std::any::type_name::<R>()
.split("::")
.last()
.unwrap()
.split('>')
.next()
.unwrap();
let file_name = format!("./goldenfiles/examples/{}", circuit_name);
let path = Path::new(&file_name);
let mut f =
File::create(path).unwrap_or_else(|_| panic!("Could not create file {}", file_name));
writeln!(f, "{:#?}", cost_model(relation, None))
.unwrap_or_else(|_| panic!("Could not write to file {}", file_name));
}
pub fn filecoin_srs(k: u32) -> ParamsKZG<Bls12> {
assert!(k <= 19, "We don't have an SRS for circuits of bit size {k}");
let srs_dir = env::var("SRS_DIR").unwrap_or("./examples/assets".into());
let srs_path = format!("{srs_dir}/bls_filecoin_2p{k:?}");
let mut fetching_path = srs_path.clone();
let downsize = !Path::new(fetching_path.as_str()).exists();
if downsize {
fetching_path = format!("{srs_dir}/bls_filecoin_2p19")
}
let params_fs = File::open(Path::new(&fetching_path))
.unwrap_or_else(|_| panic!("\nIt seems you have not downloaded and/or parsed the SRS from filecoin. Either download it with (make sure you are under the directory `zk_stdlib/` first):
* `curl -L -o {srs_dir}/bls_filecoin_2p19 https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19`
or, if you don't trust the source, download it from IPFS and parse it (this might take a couple of minutes):
* Download the SRS `curl -L -o {srs_dir}/phase1radix2m19 https://trusted-setup.filecoin.io/phase1/phase1radix2m19`
* Run the binary to parse it `cargo run --example parse_filecoin_srs --release`
\n"));
let mut params = ParamsKZG::read_custom::<_>(
&mut BufReader::new(params_fs),
SerdeFormat::RawBytesUnchecked,
)
.expect("Failed to read params");
if downsize {
params.downsize(k);
let mut buf = Vec::new();
params
.write_custom(&mut buf, SerdeFormat::RawBytesUnchecked)
.expect("Failed to write params");
let mut file = File::create(srs_path).expect("Failed to create file");
file.write_all(&buf[..]).expect("Failed to write params to file");
}
params
}