use super::relation::ExecutionRelation;
use super::state::StateStatement;
use super::statement::{ExecutionStatement, NounToken};
const KEY_DOMAIN: &[u8] = b"zheng-vk-program-v1";
const DIGEST_DOMAIN: &[u8] = b"zheng-vk-relation-v1";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatementKind {
Execution,
State { root_in_subject: bool },
}
pub fn program_key(program: &[NounToken], inputs: usize, kind: StatementKind) -> [u8; 32] {
let mut h = hemera::Hasher::new();
h.update(KEY_DOMAIN);
h.update(&match kind {
StatementKind::Execution => [0u8, 0],
StatementKind::State { root_in_subject } => [1u8, u8::from(root_in_subject)],
});
h.update(&(inputs as u64).to_le_bytes());
h.update(&(program.len() as u64).to_le_bytes());
let mut buf = Vec::with_capacity(program.len() * 9);
for token in program {
match token {
NounToken::Atom(v) => {
buf.push(0);
buf.extend_from_slice(&v.to_le_bytes());
}
NounToken::Pair => buf.push(1),
}
}
h.update(&buf);
*h.finalize().as_bytes()
}
#[derive(Clone, Debug)]
pub struct VerifyingKey {
program_key: [u8; 32],
digest: [u8; 32],
relation: ExecutionRelation,
}
impl VerifyingKey {
pub fn for_execution(statement: &ExecutionStatement) -> Result<Self, String> {
let relation = statement.relation()?;
Ok(Self::new(statement.program_key(), relation))
}
pub fn for_state(statement: &StateStatement) -> Result<Self, String> {
let relation = statement.relation()?;
Ok(Self::new(statement.program_key(), relation))
}
pub(crate) fn new(program_key: [u8; 32], relation: ExecutionRelation) -> Self {
let digest = relation_digest(&program_key, &relation);
Self {
program_key,
digest,
relation,
}
}
pub fn program_key(&self) -> [u8; 32] {
self.program_key
}
pub fn digest(&self) -> [u8; 32] {
self.digest
}
pub(crate) fn relation(&self) -> &ExecutionRelation {
&self.relation
}
pub(crate) fn check(&self, key: [u8; 32]) -> Result<&Self, String> {
if self.program_key != key {
return Err("verifying key was derived for another program or shape".into());
}
Ok(self)
}
}
fn relation_digest(program_key: &[u8; 32], r: &ExecutionRelation) -> [u8; 32] {
fn put(buf: &mut Vec<u8>, mut v: u64) {
while v >= 0x80 {
buf.push((v as u8) | 0x80);
v >>= 7;
}
buf.push(v as u8);
}
fn coeff(buf: &mut Vec<u8>, c: nebu::Goldilocks) {
let c = c.as_u64();
let p = nebu::field::P;
put(
buf,
if c <= p / 2 {
c << 1
} else {
((p - c) << 1) - 1
},
);
}
let i = &r.instance;
let nnz: usize = i
.matrices
.iter()
.flat_map(|m| m.entries.iter().map(Vec::len))
.sum();
let mut buf: Vec<u8> = Vec::with_capacity(DIGEST_DOMAIN.len() + 64 + 8 * nnz);
buf.extend_from_slice(DIGEST_DOMAIN);
buf.extend_from_slice(program_key);
for v in [i.num_rows, i.num_cols, i.matrices.len()] {
put(&mut buf, v as u64);
}
for m in &i.matrices {
put(&mut buf, m.rows as u64);
put(&mut buf, m.cols as u64);
put(&mut buf, m.entries.len() as u64);
for row in &m.entries {
put(&mut buf, row.len() as u64);
for &(c, v) in row {
put(&mut buf, c as u64);
coeff(&mut buf, v);
}
}
}
put(&mut buf, i.multisets.len() as u64);
for set in &i.multisets {
put(&mut buf, set.len() as u64);
for &m in set {
put(&mut buf, m as u64);
}
}
put(&mut buf, i.coeffs.len() as u64);
for &c in &i.coeffs {
coeff(&mut buf, c);
}
for list in [&r.input_indices, &r.output_indices] {
put(&mut buf, list.len() as u64);
for &x in list {
put(&mut buf, x as u64);
}
}
put(&mut buf, r.cost_index as u64);
put(&mut buf, r.max_cost);
put(&mut buf, r.lookups.len() as u64);
for l in &r.lookups {
for x in [
l.active,
l.root[0],
l.root[1],
l.root[2],
l.root[3],
l.namespace,
l.key,
l.value,
] {
put(&mut buf, x as u64);
}
}
tree_digest(&buf)
}
const LEAF_BYTES: usize = 1024;
fn tree_digest(bytes: &[u8]) -> [u8; 32] {
let chunks = bytes.len().div_ceil(LEAF_BYTES).max(1);
let count = chunks.next_power_of_two();
let tree = lens::rspcs::merkle::Tree::build_on_cpu(count, |i| {
let start = (i * LEAF_BYTES).min(bytes.len());
let end = ((i + 1) * LEAF_BYTES).min(bytes.len());
bytes[start..end].to_vec()
});
*tree.root().as_bytes()
}
#[cfg(test)]
#[path = "vk_tests.rs"]
mod tests;