use sha2::{Digest, Sha256};
use crate::audit_law::{AuditLaw, Clause, Measurements};
pub use alice_zip::law::LAW_ID_DOMAIN;
pub const AUDIT_LAW_KIND: &[u8] = b"lol.law.audit";
pub const RESEARCH_LAW_KIND: &[u8] = b"lol.law.research/v1";
mod tag {
pub const BYTES: u8 = 1;
pub const U32: u8 = 2;
pub const U64: u8 = 3;
pub const F64: u8 = 4;
pub const STR: u8 = 5;
}
pub const LOL_SEMANTICS_PINS: &[(&str, [u8; 32])] = &[
("alice-det-math", alice_det_math::SEMANTICS_ID),
(
"audit verdict order: evidence -> ranges -> expectations",
hex32("14e30eaa97ad76260f022b28f4fc15f06f9df779256439078289400b8ce11e64"),
),
(
"input reading: whole-input typed x-input",
hex32("1bf362f10e2fff2a3a620df006515f08940c4770037cfe70ee3354a5c1466f50"),
),
(
"research expressions: functions and argument counts",
hex32("ee1b39dde2b1e577892f19daee2fb514a7b43f56be7d2211061de1ddac28fad3"),
),
(
"audit derivations: count / distinct / set, x-at-least",
hex32("4ad8aec627d25fe2512d66cdff5827f2c384a596b074b0e339cda37954ee9fc3"),
),
];
pub const LOL_SEMANTICS_BY_DET_MATH: &[([u8; 32], [u8; 32])] = &[
(
hex32("135c407d4249c1c200e4d1be79a12e848ce5c355fc3a918da965849e697ef000"),
hex32("403359f60a227113988db2cc16d2c9b40a2cb3d725f06cebb6a57828ee2a8421"),
),
(
hex32("85e0884623b154a3b2b264374907b6cbac09307ca3f9a08339e48c567d3cb315"),
hex32("d81c2cbbf7f010b7b3368c461d0a4ba60345fae302e9991fbd992d35dc3a7002"),
),
];
pub const LOL_SEMANTICS_ID: [u8; 32] = semantics_id_for(&alice_det_math::SEMANTICS_ID);
#[must_use]
pub const fn semantics_id_for(det_math: &[u8; 32]) -> [u8; 32] {
let mut i = 0;
while i < LOL_SEMANTICS_BY_DET_MATH.len() {
if same32(&LOL_SEMANTICS_BY_DET_MATH[i].0, det_math) {
return LOL_SEMANTICS_BY_DET_MATH[i].1;
}
i += 1;
}
panic!("alice-det-math SEMANTICS_ID has no row in LOL_SEMANTICS_BY_DET_MATH: add its fold");
}
#[must_use]
pub fn known_semantics_id(id: &[u8; 32]) -> Option<[u8; 32]> {
LOL_SEMANTICS_BY_DET_MATH
.iter()
.find(|(_, lol)| lol == id)
.map(|(det_math, _)| *det_math)
}
pub(crate) const fn same32(a: &[u8; 32], b: &[u8; 32]) -> bool {
let mut i = 0;
while i < 32 {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
#[must_use]
pub const fn hex32(s: &str) -> [u8; 32] {
let b = s.as_bytes();
assert!(b.len() == 64, "32 byte = 16 進 64 文字");
let mut out = [0_u8; 32];
let mut i = 0;
while i < 32 {
out[i] = (nibble(b[i * 2]) << 4) | nibble(b[i * 2 + 1]);
i += 1;
}
out
}
const fn nibble(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
_ => panic!("16 進でない文字"),
}
}
#[must_use]
pub fn fold_semantics_pins(pins: &[(&str, [u8; 32])]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(LAW_ID_DOMAIN);
h.update(b"lol.semantics");
for (name, id) in pins {
length_prefixed(&mut h, name.as_bytes());
h.update(id);
}
h.finalize().into()
}
#[must_use]
pub fn audit_verdict_order_fingerprint() -> [u8; 32] {
let law = AuditLaw::new(
"fingerprint".to_owned(),
vec![
Clause::Evidence {
metric: "n".to_owned(),
},
Clause::Expect {
metric: "bad".to_owned(),
value: 0.0,
tolerance: 0.0,
},
Clause::Range {
key: "k".to_owned(),
values: vec!["1".to_owned(), "2".to_owned()],
},
],
);
let cases = [
Measurements::new(),
Measurements::new()
.with_number("n", 0.0)
.with_number("bad", 0.0)
.with_range("k", &["1", "2"]),
Measurements::new()
.with_number("n", 3.0)
.with_number("bad", 1.0),
Measurements::new()
.with_number("n", 3.0)
.with_number("bad", 0.0)
.with_range("k", &["1", "2"]),
Measurements::new()
.with_number("n", -1.0)
.with_number("bad", 0.0)
.with_range("k", &["1", "2"]),
Measurements::new()
.with_number("n", 3.0)
.with_number("bad", 0.0)
.with_range("k", &["2", "1", "2"]),
Measurements::new()
.with_number("n", 3.0)
.with_number("bad", f64::NAN)
.with_range("k", &["1", "2"]),
];
let mut h = Sha256::new();
h.update(LAW_ID_DOMAIN);
h.update(b"lol.audit.verdict-order");
for m in &cases {
length_prefixed(&mut h, law.evaluate(m).to_string().as_bytes());
}
h.finalize().into()
}
fn length_prefixed(h: &mut Sha256, bytes: &[u8]) {
h.update((bytes.len() as u64).to_le_bytes());
h.update(bytes);
}
#[derive(Debug, Clone)]
pub struct LawIdHasher(Sha256);
impl LawIdHasher {
#[must_use]
pub fn new(kind: &[u8], semantics_id: &[u8; 32]) -> Self {
let mut h = Sha256::new();
h.update(LAW_ID_DOMAIN);
length_prefixed(&mut h, kind);
h.update(semantics_id);
Self(h)
}
#[must_use]
pub fn bytes(mut self, v: &[u8]) -> Self {
self.0.update([tag::BYTES]);
length_prefixed(&mut self.0, v);
self
}
#[must_use]
pub fn str(mut self, v: &str) -> Self {
self.0.update([tag::STR]);
length_prefixed(&mut self.0, v.as_bytes());
self
}
#[must_use]
pub fn u32(mut self, v: u32) -> Self {
self.0.update([tag::U32]);
self.0.update(v.to_le_bytes());
self
}
#[must_use]
pub fn u64(mut self, v: u64) -> Self {
self.0.update([tag::U64]);
self.0.update(v.to_le_bytes());
self
}
#[must_use]
pub fn f64(mut self, v: f64) -> Self {
self.0.update([tag::F64]);
let bits = if v.is_nan() {
f64::NAN.to_bits()
} else {
v.to_bits()
};
self.0.update(bits.to_le_bytes());
self
}
#[must_use]
pub fn finish(self) -> [u8; 32] {
self.0.finalize().into()
}
}
impl AuditLaw {
#[must_use]
pub fn law_id(&self, semantics_id: &[u8; 32]) -> [u8; 32] {
let mut e = LawIdHasher::new(AUDIT_LAW_KIND, semantics_id).str(self.name());
e = e.u64(self.clauses().len() as u64);
for c in self.clauses() {
e = match c {
Clause::Evidence { metric } => e.u32(0).str(metric),
Clause::Expect {
metric,
value,
tolerance,
} => e.u32(1).str(metric).f64(*value).f64(*tolerance),
Clause::Range { key, values } => {
let mut e = e.u32(2).str(key).u64(values.len() as u64);
for v in values {
e = e.str(v);
}
e
}
};
}
let mut inputs: Vec<_> = self.inputs().iter().collect();
inputs.sort_by(|a, b| a.0.cmp(&b.0));
for (name, ty) in inputs {
e = e.u32(3).str(name).str(&ty.canonical());
}
let mut metrics: Vec<_> = self.metrics().iter().collect();
metrics.sort_by(|a, b| a.0.cmp(&b.0));
for (name, expr) in metrics {
e = e.u32(4).str(name).str(&expr.canonical());
}
let mut floors: Vec<_> = self.at_least().iter().collect();
floors.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.total_cmp(&b.1)));
for (name, n) in floors {
e = e.u32(5).str(name).f64(*n);
}
e.finish()
}
}