use super::ExecutionNoun as N;
use hemera::field::Goldilocks as H;
use std::sync::OnceLock;
pub const COST: u64 = 39_818;
pub const FLAGS_NODE: u64 = 2;
pub const FLAGS_ROOT: u64 = 3;
fn at(v: u64) -> N {
N::Atom(v)
}
fn pr(a: N, b: N) -> N {
N::Pair(Box::new(a), Box::new(b))
}
fn ax(n: u64) -> N {
pr(at(0), at(n))
}
fn q(x: N) -> N {
pr(at(1), x)
}
fn op(tag: u64, a: N, b: N) -> N {
pr(at(tag), pr(a, b))
}
fn compose(s: N, f: N) -> N {
op(2, s, f)
}
fn cons(a: N, b: N) -> N {
op(3, a, b)
}
fn branch(test: N, yes: N, no: N) -> N {
pr(at(4), pr(test, pr(yes, no)))
}
fn add(a: N, b: N) -> N {
op(5, a, b)
}
fn mul(a: N, b: N) -> N {
op(7, a, b)
}
fn eq(a: N, b: N) -> N {
op(9, a, b)
}
fn sub(parent: u64, rel: u64) -> u64 {
let depth = 63 - rel.leading_zeros();
(parent << depth) | (rel & ((1 << depth) - 1))
}
fn tree16(mut lanes: Vec<N>, join: fn(N, N) -> N) -> N {
debug_assert_eq!(lanes.len(), 16);
while lanes.len() > 1 {
let mut next = Vec::with_capacity(lanes.len() / 2);
let mut it = lanes.into_iter();
while let (Some(a), Some(b)) = (it.next(), it.next()) {
next.push(join(a, b));
}
lanes = next;
}
lanes.pop().expect("one root")
}
const SBOX: u64 = 0;
const M4: u64 = 1;
const EXT: u64 = 2;
const INT: u64 = 3;
const INVLOOP: u64 = 4;
const INV: u64 = 5;
const FULL: u64 = 6;
const PARTIAL: u64 = 7;
const ROUNDS: u64 = 8;
fn lib(j: u64) -> u64 {
sub(3, (1 << (j + 2)) - 2)
}
fn call(j: u64, data: N) -> N {
compose(cons(data, ax(3)), ax(lib(j)))
}
fn sbox() -> N {
let x3 = mul(ax(2), mul(ax(2), ax(2)));
let x6 = compose(cons(x3, ax(3)), q(mul(ax(2), ax(2))));
mul(ax(2), x6)
}
fn m4() -> N {
let term = |k: u64, axis: u64| {
if k == 1 {
ax(axis)
} else {
mul(q(at(k)), ax(axis))
}
};
let lin = |c: [u64; 4]| {
add(
add(term(c[0], 8), term(c[1], 9)),
add(term(c[2], 10), term(c[3], 11)),
)
};
cons(
cons(lin([2, 3, 1, 1]), lin([1, 2, 3, 1])),
cons(lin([1, 1, 2, 3]), lin([3, 1, 1, 2])),
)
}
fn ext() -> N {
let blocks = cons(
cons(call(M4, ax(8)), call(M4, ax(9))),
cons(call(M4, ax(10)), call(M4, ax(11))),
);
let out = tree16(
(0..16).map(i add(ax(64 + i), ax(20 + i % 4))).collect(),
cons,
);
let sum = k: u64 add(add(ax(32 + k), ax(36 + k)), add(ax(40 + k), ax(44 + k)));
let sums = cons(cons(sum(0), sum(1)), cons(sum(2), sum(3)));
let with_sums = compose(cons(cons(ax(2), sums), ax(3)), q(out));
compose(cons(blocks, ax(3)), q(with_sums))
}
fn int() -> N {
let mut total: Vec<N> = (0..16).map(i ax(32 + i)).collect();
while total.len() > 1 {
let mut next = vec![];
let mut it = total.into_iter();
while let (Some(a), Some(b)) = (it.next(), it.next()) {
next.push(add(a, b));
}
total = next;
}
let out = tree16(
(0..16)
.map(i {
let d = hemera::field::MATRIX_DIAG_16[i as usize].as_canonical_u64();
add(mul(q(at(d)), ax(64 + i)), ax(5))
})
.collect(),
cons,
);
compose(cons(cons(ax(2), total.pop().expect("sum")), ax(3)), q(out))
}
fn invloop() -> N {
let again =
|acc: N, runs: N| compose(cons(cons(acc, cons(ax(10), runs)), ax(3)), ax(lib(INVLOOP)));
let factor = branch(ax(45), q(at(1)), ax(10));
let step = again(
mul(mul(ax(4), ax(4)), factor),
cons(cons(op(6, ax(44), q(at(1))), ax(45)), ax(23)),
);
let next_run = again(ax(4), ax(23));
branch(
eq(ax(45), q(at(2))),
ax(4),
branch(eq(ax(44), q(at(0))), next_run, step),
)
}
fn inv() -> N {
let runs = [(31, 1), (1, 0), (32, 1), (0, 2)]
.into_iter()
.rev()
.fold(at(0), |rest, (n, b)| pr(pr(at(n), at(b)), rest));
compose(
cons(cons(q(at(1)), cons(ax(2), q(runs))), ax(3)),
ax(lib(INVLOOP)),
)
}
fn full() -> N {
let lanes = (0..16)
.map(i call(SBOX, add(ax(64 + i), ax(80 + i))))
.collect();
call(EXT, tree16(lanes, cons))
}
fn partial() -> N {
let lanes = (0..16)
.map(i {
if i == 0 {
call(INV, add(ax(64), ax(5)))
} else {
ax(64 + i)
}
})
.collect();
call(INT, tree16(lanes, cons))
}
fn rounds() -> N {
let with_rc = j compose(cons(cons(ax(4), ax(21)), ax(3)), ax(lib(j)));
let stepped = branch(
eq(ax(20), q(at(0))),
with_rc(FULL),
branch(eq(ax(20), q(at(1))), with_rc(PARTIAL), call(EXT, ax(4))),
);
let next = compose(cons(cons(stepped, ax(11)), ax(3)), ax(lib(ROUNDS)));
branch(eq(ax(20), q(at(2))), ax(4), next)
}
fn schedule() -> N {
let rc = hemera::constants::ROUND_CONSTANTS_U64;
let mut list = pr(pr(at(2), at(0)), at(0));
for round in (0..24).rev() {
let entry = if (4..20).contains(&round) {
pr(at(1), at(rc[128 + round - 4]))
} else {
let offset = if round < 4 {
round * 16
} else {
(round - 16) * 16
};
pr(
at(0),
tree16((0..16).map(l at(rc[offset + l])).collect(), pr),
)
};
list = pr(entry, list);
}
pr(pr(at(3), at(0)), list)
}
fn library() -> N {
[
sbox(),
m4(),
ext(),
int(),
invloop(),
inv(),
full(),
partial(),
rounds(),
]
.into_iter()
.rev()
.fold(at(0), rest, f pr(f, rest))
}
fn build() -> N {
let state = tree16(
(0..16)
.map(i match i {
0..=7 => ax(16 + i),
9 => ax(3),
_ => q(at(0)),
})
.collect(),
cons,
);
let start = cons(cons(state, q(schedule())), q(library()));
let run = compose(start, q(compose(ax(1), ax(lib(ROUNDS)))));
compose(run, q(ax(4)))
}
pub fn formula() -> &'static N {
static F: OnceLock<N> = OnceLock::new();
F.get_or_init(build)
}
pub(crate) fn is_node(f: &N) -> bool {
f == formula()
}
pub fn native(left: [u64; 4], right: [u64; 4], flags: u64) -> [u64; 4] {
let mut state = [H::ZERO; 16];
for i in 0..4 {
state[i] = H::new(left[i]);
state[4 + i] = H::new(right[i]);
}
state[9] = H::new(flags);
hemera::permutation::permute(&mut state);
core::array::from_fn(i state[i].as_canonical_u64())
}
pub fn digest_noun(d: [u64; 4]) -> N {
pr(pr(at(d[0]), at(d[1])), pr(at(d[2]), at(d[3])))
}
#[path = "node_jet.rs"]
pub mod jet;
#[cfg(test)]
#[path = "node_tests.rs"]
mod tests;