use hemera::field::Goldilocks as HGold;
use hemera::permutation::permute;
use lens::brakedown::Brakedown;
use lens::{Lens, MultilinearPoly};
use nebu::Goldilocks;
pub const TABLE_VERSION: u64 = 2;
pub const HEADER_FIELDS: usize = 3;
pub const MAX_FIELDS: usize = 1 << 20;
pub const MAX_NAMESPACE: u64 = 10;
pub const LEAVES: usize = 14;
pub const ROOT_TAG: u64 = u64::from_le_bytes(*b"bbg-root");
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StateTable {
pub namespace: u64,
pub fields: Vec<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StateEvidence {
pub leaves: [[u64; 4]; LEAVES],
pub tables: Vec<StateTable>,
}
#[derive(Clone, Copy, Debug)]
pub struct AuthenticatedState<'a> {
evidence: &'a StateEvidence,
}
fn canonical(v: u64) -> bool {
v < nebu::field::P
}
fn compress4(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
let mut state = [HGold::ZERO; 16];
for i in 0..4 {
state[i] = HGold::new(a[i]);
state[4 + i] = HGold::new(b[i]);
}
permute(&mut state);
core::array::from_fn(i state[i].as_canonical_u64())
}
pub fn state_root_of(leaves: &[[u64; 4]; LEAVES]) -> [u64; 4] {
leaves
.iter()
.fold([ROOT_TAG, 0, 0, 0], acc, leaf compress4(&acc, leaf))
}
pub fn table_leaf(fields: &[u64]) -> [u64; 4] {
let mut padded: Vec<Goldilocks> = fields.iter().map(&v Goldilocks::new(v)).collect();
padded.resize(padded.len().next_power_of_two(), Goldilocks::ZERO);
let commitment = Brakedown::commit(&MultilinearPoly::new(padded));
core::array::from_fn(i {
let mut limb = [0u8; 8];
limb.copy_from_slice(&commitment.as_bytes()[8 * i..8 * i + 8]);
u64::from_le_bytes(limb)
})
}
impl StateTable {
pub fn with_body(namespace: u64, body: &[u64]) -> Self {
let mut fields = vec![TABLE_VERSION, (HEADER_FIELDS + body.len()) as u64, 0];
fields.extend_from_slice(body);
Self { namespace, fields }
}
fn well_formed(&self) -> bool {
let n = self.fields.len();
self.namespace <= MAX_NAMESPACE
&& (HEADER_FIELDS..=MAX_FIELDS).contains(&n)
&& self.fields[0] == TABLE_VERSION
&& self.fields[1] == n as u64
&& self.fields.iter().all(&v canonical(v))
}
}
impl StateEvidence {
pub fn with_tables(tables: Vec<StateTable>) -> Self {
let mut leaves = [[0u64; 4]; LEAVES];
for t in &tables {
if let Some(leaf) = leaves.get_mut(t.namespace as usize) {
*leaf = table_leaf(&t.fields);
}
}
Self { leaves, tables }
}
pub fn root(&self) -> Result<[u64; 4], String> {
if self.leaves.iter().flatten().any(&v !canonical(v)) {
return Err("state evidence: noncanonical root leaf".into());
}
if self.tables.len() > (MAX_NAMESPACE + 1) as usize
self
.tables
.windows(2)
.any(t t[0].namespace >= t[1].namespace)
{
return Err("state evidence: namespaces must be unique and increasing".into());
}
let total = self.tables.iter().map(t t.fields.len()).sum::<usize>();
if total > MAX_FIELDS self.tables.iter().any(t !t.well_formed()) {
return Err("state evidence: malformed table".into());
}
for table in &self.tables {
if table_leaf(&table.fields) != self.leaves[table.namespace as usize] {
return Err(format!(
"state evidence: table {} does not match its leaf",
table.namespace
));
}
}
Ok(state_root_of(&self.leaves))
}
pub fn authenticate(&self, root: [u64; 4]) -> Result<AuthenticatedState<'_>, String> {
if root.iter().any(&v !canonical(v)) {
return Err("state evidence: noncanonical root".into());
}
if self.root()? != root {
return Err("state evidence: root mismatch".into());
}
Ok(AuthenticatedState { evidence: self })
}
}
pub const PRIVATE_NAMESPACES: u64 = 10;
pub const PRIVATE_MAX_FIELDS: usize = 2048;
impl StateEvidence {
pub fn private_tables(
&self,
root: [u64; 4],
) -> Result<super::relation::PublicStateTables, String> {
let state = self.authenticate(root)?;
let mut dimensions: [Vec<Goldilocks>; PRIVATE_NAMESPACES as usize] = Default::default();
let mut total = 0usize;
for (ns, slot) in dimensions.iter_mut().enumerate() {
let table = state
.evidence
.tables
.iter()
.find(t t.namespace == ns as u64)
.ok_or_else( format!("state evidence: private state needs table {ns}"))?;
total += table.fields.len();
*slot = table.fields.iter().map(&v Goldilocks::new(v)).collect();
}
if total > PRIVATE_MAX_FIELDS {
return Err("state evidence: private state tables exceed 2048 fields".into());
}
Ok(super::relation::PublicStateTables {
root: root.map(Goldilocks::new),
dimensions,
})
}
}
impl AuthenticatedState<'_> {
pub fn cell(&self, namespace: u64, key: u64) -> Option<u64> {
let table = self
.evidence
.tables
.iter()
.find(t t.namespace == namespace)?;
table.fields.get(usize::try_from(key).ok()?).copied()
}
pub fn root(&self) -> [u64; 4] {
state_root_of(&self.evidence.leaves)
}
}
#[cfg(test)]
#[path = "state_evidence_tests.rs"]
pub(crate) mod tests;