use cyber_hemera::hash as hemera_hash;
use crate::vdf::{self, VdfProof};
const DOMAIN: &[u8] = b"foculus-beacon-v0";
pub const GENESIS_PREV: [u8; 32] = [0u8; 32];
pub const DEFAULT_OUTER_T: u64 = 256;
pub const TEST_OUTER_T: u64 = 32;
#[derive(Clone, Debug)]
pub struct BeaconArtifact {
pub epoch: u64,
pub prev: [u8; 32],
pub claims_root: [u8; 32],
pub signal_root: [u8; 32],
pub outer_vdf: VdfProof,
pub beacon: [u8; 32],
}
pub fn claims_root(claim_ids: &span>; 32) -> [u8; 32] {
let mut sorted = claim_ids.to_vec();
sorted.sort_unstable();
let mut buf = Vec::with_capacity(8 + sorted.len() * 32);
buf.extend_from_slice(&(sorted.len() as u64).to_le_bytes());
for id in &sorted {
buf.extend_from_slice(id);
}
hash32(&buf)
}
pub fn signal_vdf_root(vdf_outputs: &[u64]) -> [u8; 32] {
if vdf_outputs.is_empty() {
return [0u8; 32];
}
let mut sorted = vdf_outputs.to_vec();
sorted.sort_unstable();
let mut buf = Vec::with_capacity(8 + sorted.len() * 8);
buf.extend_from_slice(&(sorted.len() as u64).to_le_bytes());
for o in &sorted {
buf.extend_from_slice(&o.to_le_bytes());
}
hash32(&buf)
}
pub fn collect_signal_outputs(proofs: &[VdfProof]) -> Vec<u64> {
proofs
.iter()
.filter(|p| vdf::verify(p))
.map(|p| p.output)
.collect()
}
pub fn open_beacon(
epoch: u64,
prev: &[u8; 32],
claims_root: &[u8; 32],
signal_outputs: &[u64],
outer_t: u64,
) -> BeaconArtifact {
let signal_root = signal_vdf_root(signal_outputs);
let vdf_in = if signal_outputs.is_empty() {
vdf::challenge_from_hash(prev)
} else {
vdf::challenge_from_hash(&signal_root)
};
let outer_vdf = vdf::evaluate(vdf_in, outer_t);
let beacon = finalize_beacon(epoch, prev, claims_root, &signal_root, outer_vdf.output);
BeaconArtifact {
epoch,
prev: *prev,
claims_root: *claims_root,
signal_root,
outer_vdf,
beacon,
}
}
pub fn verify_beacon(art: &BeaconArtifact) -> bool {
if !vdf::verify(&art.outer_vdf) {
return false;
}
let expected_in = if art.signal_root == [0u8; 32] {
vdf::challenge_from_hash(&art.prev)
} else {
vdf::challenge_from_hash(&art.signal_root)
};
if art.outer_vdf.input != expected_in {
return false;
}
let b = finalize_beacon(
art.epoch,
&art.prev,
&art.claims_root,
&art.signal_root,
art.outer_vdf.output,
);
b == art.beacon
}
pub fn beacon(epoch: u64, prev: &[u8; 32], claims_root: &[u8; 32]) -> [u8; 32] {
open_beacon(epoch, prev, claims_root, &[], DEFAULT_OUTER_T).beacon
}
pub fn advance_empty(epoch: u64, prev: &[u8; 32]) -> [u8; 32] {
open_beacon(epoch, prev, &[0u8; 32], &[], DEFAULT_OUTER_T).beacon
}
fn finalize_beacon(
epoch: u64,
prev: &[u8; 32],
claims_root: &[u8; 32],
signal_root: &[u8; 32],
outer_output: u64,
) -> [u8; 32] {
let mut buf = Vec::with_capacity(DOMAIN.len() + 8 + 32 * 3 + 8);
buf.extend_from_slice(DOMAIN);
buf.extend_from_slice(&epoch.to_le_bytes());
buf.extend_from_slice(prev);
buf.extend_from_slice(claims_root);
buf.extend_from_slice(signal_root);
buf.extend_from_slice(&outer_output.to_le_bytes());
hash32(&buf)
}
fn hash32(buf: &[u8]) -> [u8; 32] {
*hemera_hash(buf)
.as_bytes()
.first_chunk::<32>()
.unwrap_or(&[0u8; 32])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_beacon_verifies() {
let cr = claims_root(&[[1u8; 32], [2u8; 32]]);
let art = open_beacon(1, &GENESIS_PREV, &cr, &[42, 7, 99], TEST_OUTER_T);
assert!(verify_beacon(&art));
assert_ne!(art.beacon, [0u8; 32]);
}
#[test]
fn quiet_epoch_uses_prev() {
let a = open_beacon(1, &GENESIS_PREV, &[0u8; 32], &[], TEST_OUTER_T);
let b = open_beacon(1, &GENESIS_PREV, &[0u8; 32], &[], TEST_OUTER_T);
assert_eq!(a.beacon, b.beacon);
assert!(verify_beacon(&a));
let c = open_beacon(2, &a.beacon, &[0u8; 32], &[], TEST_OUTER_T);
assert_ne!(c.beacon, a.beacon);
assert!(verify_beacon(&c));
}
#[test]
fn signal_set_changes_beacon() {
let cr = claims_root(&[[9u8; 32]]);
let a = open_beacon(1, &GENESIS_PREV, &cr, &[1, 2, 3], TEST_OUTER_T);
let b = open_beacon(1, &GENESIS_PREV, &cr, &[1, 2, 4], TEST_OUTER_T);
assert_ne!(a.beacon, b.beacon);
}
#[test]
fn claims_order_independent() {
let a = claims_root(&[[1u8; 32], [2u8; 32]]);
let b = claims_root(&[[2u8; 32], [1u8; 32]]);
assert_eq!(a, b);
}
#[test]
fn tampered_vdf_fails() {
let cr = claims_root(&[[1u8; 32]]);
let mut art = open_beacon(1, &GENESIS_PREV, &cr, &[5], TEST_OUTER_T);
art.outer_vdf.output ^= 1;
assert!(!verify_beacon(&art));
}
#[test]
fn invalid_signal_vdf_dropped() {
let good = vdf::evaluate(7, 16);
let mut bad = good.clone();
bad.output ^= 1;
let outs = collect_signal_outputs(&[good, bad]);
assert_eq!(outs.len(), 1);
}
}