use std::fs::{self, File, read};
use std::io::prelude::*;
use std::io::{self, ErrorKind};
use std::path::PathBuf;
use blake3::Hasher;
use serde::{Deserialize, Serialize};
use tracing::info;
use version_check::Version;
use crate::{
Theme, extension, file_name, file_stem, get_rusk_circuits_dir,
get_rusk_keys_dir,
};
#[derive(Debug, Clone, PartialEq)]
pub struct Circuit {
id: [u8; 32],
id_str: String,
circuit: Vec<u8>,
metadata: Metadata,
}
#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
struct Metadata {
plonk_version: Option<String>,
name: Option<String>,
}
impl Circuit {
pub fn new(
circuit: Vec<u8>,
plonk_version: String,
name: Option<String>,
) -> io::Result<Self> {
let id = compute_id(&circuit, &plonk_version)?;
Ok(Self {
id,
id_str: hex::encode(id),
circuit,
metadata: Metadata {
plonk_version: Some(plonk_version),
name,
},
})
}
pub fn from_stored(id: [u8; 32]) -> io::Result<Self> {
let mut file = get_rusk_circuits_dir()?;
let id_str = hex::encode(id);
file.push(&id_str);
file.set_extension("cd");
let circuit = match &file.exists() {
true => read(file),
false => {
Err(io::Error::new(ErrorKind::NotFound, "Circuit not found"))
}
}?;
let circuit = Self {
id,
id_str,
circuit,
metadata: Metadata::from_stored(&id)?,
};
if let Some(result) = circuit.check_id() {
if !result {
return Err(io::Error::new(
ErrorKind::InvalidData,
"The stored circuit id is incorrect",
));
}
}
Ok(circuit)
}
pub fn from_name(name: impl AsRef<str>) -> io::Result<Self> {
let id = search_id(name.as_ref())?;
Circuit::from_stored(id)
}
pub fn check_id(&self) -> Option<bool> {
match self.plonk_version() {
None => None,
Some(version) => {
let computed_id = compute_id(self.circuit(), version)
.expect("plonk-version of a stored circuit to be valid");
Some(computed_id == *self.id())
}
}
}
pub fn store(&self) -> io::Result<()> {
self.metadata.update_or_store(&self.id)?;
let mut file = get_rusk_circuits_dir()?;
file.push(self.id_str());
let cd_file = file.with_extension("cd");
File::create(&cd_file)?.write_all(&self.circuit)?;
info!(
"{} {}",
Theme::default().info("Cached"),
file_name(&cd_file)
.expect("At this point we know that the file is valid")
);
Ok(())
}
pub fn circuit(&self) -> &[u8] {
&self.circuit
}
pub fn id(&self) -> &[u8; 32] {
&self.id
}
pub fn id_str(&self) -> &str {
&self.id_str
}
pub fn name(&self) -> &str {
self.metadata.name().unwrap_or(self.id_str())
}
pub fn plonk_version(&self) -> Option<&str> {
self.metadata.plonk_version.as_deref()
}
pub fn get_compressed(&self) -> &[u8] {
&self.circuit
}
pub fn get_prover(&self) -> io::Result<Vec<u8>> {
let mut file = get_rusk_keys_dir()?;
file.push(self.id_str());
file.set_extension("pk");
let pk = match &file.exists() {
true => read(file),
false => {
Err(io::Error::new(ErrorKind::NotFound, "ProverKey not found"))
}
}?;
Ok(pk)
}
pub fn get_verifier(&self) -> io::Result<Vec<u8>> {
let mut file = get_rusk_keys_dir()?;
file.push(self.id_str());
file.set_extension("vd");
let vd = match &file.exists() {
true => read(file),
false => Err(io::Error::new(
ErrorKind::NotFound,
"VerifierData not found",
)),
}?;
Ok(vd)
}
pub fn get_keys(&self) -> io::Result<(Vec<u8>, Vec<u8>)> {
Ok((self.get_prover()?, self.get_verifier()?))
}
pub fn add_keys(&self, pk: Vec<u8>, vd: Vec<u8>) -> io::Result<()> {
let mut file = get_rusk_keys_dir()?;
file.push(self.id_str());
let pk_file = file.with_extension("pk");
let vd_file = file.with_extension("vd");
File::create(pk_file)?.write_all(&pk)?;
File::create(vd_file)?.write_all(&vd)?;
Ok(())
}
pub fn clean(&self) -> io::Result<()> {
let circuit_files: Vec<PathBuf> =
fs::read_dir(get_rusk_circuits_dir()?)?
.flatten()
.map(|entry| entry.path())
.filter(|file| file_stem(file) == Some(self.id_str()))
.collect();
for file in circuit_files {
info!(
"{} /circuits/{}",
Theme::default().warn("Removing"),
file_name(&file).expect("file should be valid")
);
fs::remove_file(file)?;
}
let keys_files: Vec<PathBuf> = fs::read_dir(get_rusk_keys_dir()?)?
.flatten()
.map(|entry| entry.path())
.filter(|file| file_stem(file) == Some(self.id_str()))
.collect();
for file in keys_files {
info!(
"{} /keys/{}",
Theme::default().warn("Removing"),
file_name(&file).expect("file should be valid")
);
fs::remove_file(file)?;
}
Ok(())
}
}
impl Metadata {
fn new(plonk_version: Option<String>, name: Option<String>) -> Self {
Self {
plonk_version,
name,
}
}
fn from_stored(id: &[u8; 32]) -> io::Result<Self> {
let mut file = get_rusk_circuits_dir()?;
file.push(hex::encode(id));
file.set_extension("toml");
Metadata::from_file(&file)
}
fn from_file(file: &PathBuf) -> io::Result<Self> {
let mut metadata = Metadata::new(None, None);
if file.exists() {
let content = read(file)?;
let content =
std::str::from_utf8(content.as_slice()).map_err(|e| {
io::Error::new(
ErrorKind::InvalidData,
format!(
"Couldn't read metadata for {:?}: {}",
file.file_name().expect("file exists"),
e
),
)
})?;
metadata = toml::from_str(content).map_err(|e| {
io::Error::new(
ErrorKind::InvalidData,
format!(
"Couldn't parse metadata for {:?}: {}",
file.file_name().expect("file exists"),
e
),
)
})?;
}
Ok(metadata)
}
fn name(&self) -> Option<&str> {
self.name.as_deref()
}
fn update_or_store(&self, id: &[u8; 32]) -> io::Result<()> {
let stored = Metadata::from_stored(id)?;
if self != &stored {
return self.add(id);
}
Ok(())
}
fn add(&self, id: &[u8; 32]) -> io::Result<()> {
let mut file = get_rusk_circuits_dir()?;
file.push(hex::encode(id));
file.set_extension("toml");
let toml = toml::to_string(self).map_err(|e| {
io::Error::new(
ErrorKind::InvalidData,
format!("Couldn't create string from metadata: {e}"),
)
})?;
File::create(&file)?.write_all(toml.as_bytes())?;
Ok(())
}
}
fn compute_id(circuit: &[u8], plonk_version: &str) -> io::Result<[u8; 32]> {
let (major, mut minor, _) = match Version::parse(plonk_version) {
Some(v) => v.to_mmp(),
None => {
return Err(io::Error::new(
ErrorKind::InvalidInput,
format!("couldn't parse plonk version: {plonk_version}"),
));
}
};
if major > 0 {
minor = 0;
}
let mut hasher = Hasher::new();
hasher.update(circuit);
hasher.update(&major.to_be_bytes());
hasher.update(&minor.to_be_bytes());
Ok(hasher.finalize().into())
}
fn search_id(name: &str) -> io::Result<[u8; 32]> {
let circuits_dir = get_rusk_circuits_dir()?;
let toml_files: Vec<PathBuf> = fs::read_dir(circuits_dir)?
.flatten()
.map(|entry| entry.path())
.filter(|file| extension(file) == Some("toml"))
.filter(|file| {
let metadata = Metadata::from_file(file);
match metadata {
Err(_) => false,
Ok(data) => match data.name {
Some(stored_name) => stored_name == name,
None => false,
},
}
})
.collect();
if toml_files.len() == 1 {
let id_str = file_stem(&toml_files[0]).expect("file exists");
let id = hex::decode(id_str).map_err(|e| {
io::Error::new(
ErrorKind::InvalidData,
format!("Couldn't parse id from {id_str}: {e}"),
)
})?;
if id.len() == 32 {
let mut buf = [0u8; 32];
buf.copy_from_slice(&id[0..32]);
return Ok(buf);
}
}
Err(io::Error::new(
ErrorKind::NotFound,
format!("Couldn't find circuit id for {name}"),
))
}