use super::stack::{PushValue, StackOp};
fn emit_reverse_n(n: usize) -> Vec<StackOp> {
if n <= 1 {
return vec![];
}
let mut ops = Vec::with_capacity(4 * (n - 1));
for _ in 0..n - 1 {
ops.push(StackOp::Push(PushValue::Int(1)));
ops.push(StackOp::Opcode("OP_SPLIT".into()));
}
for _ in 0..n - 1 {
ops.push(StackOp::Swap);
ops.push(StackOp::Opcode("OP_CAT".into()));
}
ops
}
fn int4be(v: usize) -> Vec<u8> {
vec![
((v >> 24) & 0xff) as u8,
((v >> 16) & 0xff) as u8,
((v >> 8) & 0xff) as u8,
(v & 0xff) as u8,
]
}
fn collect_ops(f: impl FnOnce(&mut dyn FnMut(StackOp))) -> Vec<StackOp> {
let mut ops = Vec::new();
f(&mut |op| ops.push(op));
ops
}
#[derive(Debug, Clone, Copy)]
struct SLHCodegenParams {
n: usize, h: usize, d: usize, hp: usize, a: usize, k: usize, #[allow(dead_code)]
w: usize, len: usize, len1: usize, len2: usize, }
fn slh_mk(n: usize, h: usize, d: usize, a: usize, k: usize) -> SLHCodegenParams {
let len1 = 2 * n;
let len2 = ((len1 as f64 * 15.0).log2() / 16.0_f64.log2()).floor() as usize + 1;
SLHCodegenParams {
n,
h,
d,
hp: h / d,
a,
k,
w: 16,
len: len1 + len2,
len1,
len2,
}
}
fn slh_params(key: &str) -> SLHCodegenParams {
match key {
"SHA2_128s" => slh_mk(16, 63, 7, 12, 14),
"SHA2_128f" => slh_mk(16, 66, 22, 6, 33),
"SHA2_192s" => slh_mk(24, 63, 7, 14, 17),
"SHA2_192f" => slh_mk(24, 66, 22, 8, 33),
"SHA2_256s" => slh_mk(32, 64, 8, 14, 22),
"SHA2_256f" => slh_mk(32, 68, 17, 8, 35),
_ => panic!("Unknown SLH-DSA params: {}", key),
}
}
const SLH_WOTS_HASH: u8 = 0;
const SLH_WOTS_PK: u8 = 1;
const SLH_TREE: u8 = 2;
const SLH_FORS_TREE: u8 = 3;
const SLH_FORS_ROOTS: u8 = 4;
#[cfg(test)]
#[derive(Default)]
struct SLHADRSOpts {
layer: usize,
tree: i64,
adrs_typ: u8,
keypair: i32,
chain: i32,
hash: i32,
}
#[cfg(test)]
fn slh_adrs(opts: &SLHADRSOpts) -> Vec<u8> {
let mut c = vec![0u8; 22];
c[0] = (opts.layer & 0xff) as u8;
let tr = opts.tree;
for i in 0..8 {
c[1 + 7 - i] = ((tr >> (8 * i)) & 0xff) as u8;
}
c[9] = opts.adrs_typ;
let kp = opts.keypair;
c[10] = ((kp >> 24) & 0xff) as u8;
c[11] = ((kp >> 16) & 0xff) as u8;
c[12] = ((kp >> 8) & 0xff) as u8;
c[13] = (kp & 0xff) as u8;
let ch = opts.chain;
c[14] = ((ch >> 24) & 0xff) as u8;
c[15] = ((ch >> 16) & 0xff) as u8;
c[16] = ((ch >> 8) & 0xff) as u8;
c[17] = (ch & 0xff) as u8;
let ha = opts.hash;
c[18] = ((ha >> 24) & 0xff) as u8;
c[19] = ((ha >> 16) & 0xff) as u8;
c[20] = ((ha >> 8) & 0xff) as u8;
c[21] = (ha & 0xff) as u8;
c
}
#[cfg(test)]
fn slh_adrs18(opts: &SLHADRSOpts) -> Vec<u8> {
let full = slh_adrs(&SLHADRSOpts {
layer: opts.layer,
tree: opts.tree,
adrs_typ: opts.adrs_typ,
keypair: opts.keypair,
chain: opts.chain,
hash: 0,
});
full[..18].to_vec()
}
enum HashMode {
Zero,
Stack,
}
fn emit_build_adrs18(
emit: &mut dyn FnMut(StackOp),
layer: usize,
type_: u8,
chain: usize,
ta8_depth: usize,
kp4_depth: Option<usize>,
) {
emit(StackOp::Push(PushValue::Bytes(vec![(layer & 0xff) as u8])));
let ta8_pick = ta8_depth + 1;
emit(StackOp::Push(PushValue::Int(ta8_pick as i128)));
emit(StackOp::Opcode("OP_PICK".into()));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Push(PushValue::Bytes(vec![type_ & 0xff])));
emit(StackOp::Opcode("OP_CAT".into()));
match kp4_depth {
Some(depth) => {
let kp4_pick = depth + 1;
emit(StackOp::Push(PushValue::Int(kp4_pick as i128)));
emit(StackOp::Opcode("OP_PICK".into()));
}
None => {
emit(StackOp::Push(PushValue::Bytes(vec![0, 0, 0, 0])));
}
}
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Push(PushValue::Bytes(int4be(chain))));
emit(StackOp::Opcode("OP_CAT".into()));
}
fn emit_build_adrs(
emit: &mut dyn FnMut(StackOp),
layer: usize,
type_: u8,
chain: usize,
ta8_depth: usize,
kp4_depth: Option<usize>,
hash: HashMode,
) {
match hash {
HashMode::Stack => {
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit_build_adrs18(emit, layer, type_, chain, ta8_depth - 1, kp4_depth.map(|d| d - 1));
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Opcode("OP_CAT".into()));
}
HashMode::Zero => {
emit_build_adrs18(emit, layer, type_, chain, ta8_depth, kp4_depth);
emit(StackOp::Push(PushValue::Bytes(vec![0u8; 4])));
emit(StackOp::Opcode("OP_CAT".into()));
}
}
}
struct SLHTracker<'a> {
nm: Vec<String>,
e: &'a mut dyn FnMut(StackOp),
}
#[allow(dead_code)]
impl<'a> SLHTracker<'a> {
fn new(init: &[&str], emit: &'a mut dyn FnMut(StackOp)) -> Self {
SLHTracker {
nm: init.iter().map(|s| s.to_string()).collect(),
e: emit,
}
}
fn depth(&self) -> usize {
self.nm.len()
}
fn find_depth(&self, name: &str) -> usize {
for i in (0..self.nm.len()).rev() {
if self.nm[i] == name {
return self.nm.len() - 1 - i;
}
}
panic!("SLHTracker: '{}' not on stack {:?}", name, self.nm);
}
fn has(&self, name: &str) -> bool {
self.nm.iter().any(|s| s == name)
}
fn push_bytes(&mut self, n: &str, v: Vec<u8>) {
(self.e)(StackOp::Push(PushValue::Bytes(v)));
self.nm.push(n.to_string());
}
fn push_int(&mut self, n: &str, v: i128) {
(self.e)(StackOp::Push(PushValue::Int(v)));
self.nm.push(n.to_string());
}
fn push_empty(&mut self, n: &str) {
(self.e)(StackOp::Opcode("OP_0".into()));
self.nm.push(n.to_string());
}
fn dup(&mut self, n: &str) {
(self.e)(StackOp::Dup);
self.nm.push(n.to_string());
}
fn drop(&mut self) {
(self.e)(StackOp::Drop);
if !self.nm.is_empty() {
self.nm.pop();
}
}
fn nip(&mut self) {
(self.e)(StackOp::Nip);
let len = self.nm.len();
if len >= 2 {
self.nm.remove(len - 2);
}
}
fn over(&mut self, n: &str) {
(self.e)(StackOp::Over);
self.nm.push(n.to_string());
}
fn swap(&mut self) {
(self.e)(StackOp::Swap);
let len = self.nm.len();
if len >= 2 {
self.nm.swap(len - 1, len - 2);
}
}
fn rot(&mut self) {
(self.e)(StackOp::Rot);
let len = self.nm.len();
if len >= 3 {
let r = self.nm.remove(len - 3);
self.nm.push(r);
}
}
fn op(&mut self, code: &str) {
(self.e)(StackOp::Opcode(code.into()));
}
fn roll(&mut self, d: usize) {
if d == 0 {
return;
}
if d == 1 {
self.swap();
return;
}
if d == 2 {
self.rot();
return;
}
(self.e)(StackOp::Push(PushValue::Int(d as i128)));
self.nm.push(String::new());
(self.e)(StackOp::Opcode("OP_ROLL".into()));
self.nm.pop(); let idx = self.nm.len() - 1 - d;
let r = self.nm.remove(idx);
self.nm.push(r);
}
fn pick(&mut self, d: usize, n: &str) {
if d == 0 {
self.dup(n);
return;
}
if d == 1 {
self.over(n);
return;
}
(self.e)(StackOp::Push(PushValue::Int(d as i128)));
self.nm.push(String::new());
(self.e)(StackOp::Opcode("OP_PICK".into()));
self.nm.pop(); self.nm.push(n.to_string());
}
fn to_top(&mut self, name: &str) {
let d = self.find_depth(name);
self.roll(d);
}
fn copy_to_top(&mut self, name: &str, n: &str) {
let d = self.find_depth(name);
self.pick(d, n);
}
fn to_alt(&mut self) {
self.op("OP_TOALTSTACK");
if !self.nm.is_empty() {
self.nm.pop();
}
}
fn from_alt(&mut self, n: &str) {
self.op("OP_FROMALTSTACK");
self.nm.push(n.to_string());
}
fn split(&mut self, left: &str, right: &str) {
self.op("OP_SPLIT");
if !self.nm.is_empty() {
self.nm.pop();
}
if !self.nm.is_empty() {
self.nm.pop();
}
self.nm.push(left.to_string());
self.nm.push(right.to_string());
}
fn cat(&mut self, n: &str) {
self.op("OP_CAT");
if self.nm.len() >= 2 {
self.nm.truncate(self.nm.len() - 2);
}
self.nm.push(n.to_string());
}
fn sha256(&mut self, n: &str) {
self.op("OP_SHA256");
if !self.nm.is_empty() {
self.nm.pop();
}
self.nm.push(n.to_string());
}
fn equal(&mut self, n: &str) {
self.op("OP_EQUAL");
if self.nm.len() >= 2 {
self.nm.truncate(self.nm.len() - 2);
}
self.nm.push(n.to_string());
}
fn rename(&mut self, n: &str) {
if let Some(last) = self.nm.last_mut() {
*last = n.to_string();
}
}
fn raw_block(
&mut self,
consume: &[&str],
produce: &str,
f: impl FnOnce(&mut dyn FnMut(StackOp)),
) {
for _ in consume {
if !self.nm.is_empty() {
self.nm.pop();
}
}
f(self.e);
if !produce.is_empty() {
self.nm.push(produce.to_string());
}
}
}
#[allow(dead_code)]
fn emit_slh_t(t: &mut SLHTracker, n: usize, adrs: &str, msg: &str, result: &str) {
t.to_top(adrs);
t.to_top(msg);
t.cat("_am");
t.copy_to_top("_pkSeedPad", "_psp");
t.swap();
t.cat("_pre");
t.sha256("_h32");
if n < 32 {
t.push_int("", n as i128);
t.split(result, "_tr");
t.drop();
} else {
t.rename(result);
}
}
fn emit_slh_t_raw(e: &mut dyn FnMut(StackOp), n: usize, psp_depth: usize) {
e(StackOp::Opcode("OP_CAT".into()));
let pick_depth = psp_depth - 1;
e(StackOp::Push(PushValue::Int(pick_depth as i128)));
e(StackOp::Opcode("OP_PICK".into()));
e(StackOp::Swap);
e(StackOp::Opcode("OP_CAT".into()));
e(StackOp::Opcode("OP_SHA256".into()));
if n < 32 {
e(StackOp::Push(PushValue::Int(n as i128)));
e(StackOp::Opcode("OP_SPLIT".into()));
e(StackOp::Drop);
}
}
fn slh_chain_step_then(n: usize, psp_depth: usize) -> Vec<StackOp> {
let mut ops = Vec::new();
ops.push(StackOp::Dup);
ops.push(StackOp::Push(PushValue::Int(4)));
ops.push(StackOp::Opcode("OP_NUM2BIN".into()));
ops.extend(emit_reverse_n(4));
ops.push(StackOp::Opcode("OP_FROMALTSTACK".into()));
ops.push(StackOp::Dup);
ops.push(StackOp::Opcode("OP_TOALTSTACK".into()));
ops.push(StackOp::Swap);
ops.push(StackOp::Opcode("OP_CAT".into()));
ops.push(StackOp::Push(PushValue::Int(3)));
ops.push(StackOp::Opcode("OP_ROLL".into()));
ops.push(StackOp::Opcode("OP_CAT".into()));
ops.push(StackOp::Push(PushValue::Int(psp_depth as i128)));
ops.push(StackOp::Opcode("OP_PICK".into()));
ops.push(StackOp::Swap);
ops.push(StackOp::Opcode("OP_CAT".into()));
ops.push(StackOp::Opcode("OP_SHA256".into()));
if n < 32 {
ops.push(StackOp::Push(PushValue::Int(n as i128)));
ops.push(StackOp::Opcode("OP_SPLIT".into()));
ops.push(StackOp::Drop);
}
ops.push(StackOp::Rot);
ops.push(StackOp::Opcode("OP_1SUB".into()));
ops.push(StackOp::Rot);
ops.push(StackOp::Opcode("OP_1ADD".into()));
ops
}
fn emit_slh_one_chain(
emit: &mut dyn FnMut(StackOp),
n: usize,
layer: usize,
chain_idx: usize,
psp_depth: usize,
ta8_depth: usize,
kp4_depth: usize,
) {
emit(StackOp::Push(PushValue::Int(15)));
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_SUB".into()));
emit(StackOp::Dup);
emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Swap);
emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Swap);
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit(StackOp::Swap);
emit(StackOp::Push(PushValue::Int(n as i128)));
emit(StackOp::Opcode("OP_SPLIT".into())); emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Swap);
emit(StackOp::Dup);
emit(StackOp::Push(PushValue::Int(15)));
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_SUB".into()));
let psp_d_chain = psp_depth - 1;
let ta8_d_chain = ta8_depth - 1;
let kp4_d_chain = kp4_depth - 1;
emit_build_adrs18(emit, layer, SLH_WOTS_HASH, chain_idx, ta8_d_chain, Some(kp4_d_chain));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
let then_ops = slh_chain_step_then(n, psp_d_chain);
for _ in 0..15 {
emit(StackOp::Over);
emit(StackOp::Opcode("OP_0NOTEQUAL".into()));
emit(StackOp::If {
then_ops: then_ops.clone(),
else_ops: vec![],
});
}
emit(StackOp::Drop);
emit(StackOp::Drop);
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Drop);
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Rot);
emit(StackOp::Opcode("OP_ADD".into()));
emit(StackOp::Swap);
emit(StackOp::Push(PushValue::Int(3)));
emit(StackOp::Opcode("OP_ROLL".into()));
emit(StackOp::Opcode("OP_CAT".into()));
}
fn emit_slh_wots_all(emit: &mut dyn FnMut(StackOp), p: &SLHCodegenParams, layer: usize) {
let n = p.n;
let len1 = p.len1;
let len2 = p.len2;
emit(StackOp::Swap);
emit(StackOp::Push(PushValue::Int(0)));
emit(StackOp::Opcode("OP_0".into()));
emit(StackOp::Push(PushValue::Int(3)));
emit(StackOp::Opcode("OP_ROLL".into()));
for byte_idx in 0..n {
if byte_idx < n - 1 {
emit(StackOp::Push(PushValue::Int(1)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Swap);
}
emit(StackOp::Push(PushValue::Int(0)));
emit(StackOp::Push(PushValue::Int(1)));
emit(StackOp::Opcode("OP_NUM2BIN".into()));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_BIN2NUM".into()));
emit(StackOp::Dup);
emit(StackOp::Push(PushValue::Int(16)));
emit(StackOp::Opcode("OP_DIV".into()));
emit(StackOp::Swap);
emit(StackOp::Push(PushValue::Int(16)));
emit(StackOp::Opcode("OP_MOD".into()));
if byte_idx < n - 1 {
emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Swap); emit(StackOp::Opcode("OP_TOALTSTACK".into())); } else {
emit(StackOp::Opcode("OP_TOALTSTACK".into())); }
emit_slh_one_chain(emit, n, layer, byte_idx * 2, 6, 5, 4);
if byte_idx < n - 1 {
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Swap);
emit(StackOp::Opcode("OP_TOALTSTACK".into())); } else {
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); }
emit_slh_one_chain(emit, n, layer, byte_idx * 2 + 1, 6, 5, 4);
if byte_idx < n - 1 {
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); }
}
emit(StackOp::Swap);
emit(StackOp::Dup);
emit(StackOp::Push(PushValue::Int(16)));
emit(StackOp::Opcode("OP_MOD".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit(StackOp::Dup);
emit(StackOp::Push(PushValue::Int(16)));
emit(StackOp::Opcode("OP_DIV".into()));
emit(StackOp::Push(PushValue::Int(16)));
emit(StackOp::Opcode("OP_MOD".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit(StackOp::Push(PushValue::Int(256)));
emit(StackOp::Opcode("OP_DIV".into()));
emit(StackOp::Push(PushValue::Int(16)));
emit(StackOp::Opcode("OP_MOD".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
for ci in 0..len2 {
emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Push(PushValue::Int(0)));
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit_slh_one_chain(emit, n, layer, len1 + ci, 6, 5, 4);
emit(StackOp::Swap);
emit(StackOp::Drop);
}
emit(StackOp::Swap);
emit(StackOp::Drop);
emit_build_adrs(emit, layer, SLH_WOTS_PK, 0, 2, None, HashMode::Zero);
emit(StackOp::Swap);
emit_slh_t_raw(emit, n, 4);
}
fn emit_slh_merkle(emit: &mut dyn FnMut(StackOp), p: &SLHCodegenParams, layer: usize) {
let n = p.n;
let hp = p.hp;
emit(StackOp::Push(PushValue::Int(2)));
emit(StackOp::Opcode("OP_ROLL".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
for j in 0..hp {
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit(StackOp::Push(PushValue::Int(n as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Dup);
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
if j > 0 {
emit(StackOp::Push(PushValue::Int((1i128 << j) as i128)));
emit(StackOp::Opcode("OP_DIV".into()));
}
emit(StackOp::Push(PushValue::Int(2)));
emit(StackOp::Opcode("OP_MOD".into()));
let mk_tweak_hash: Vec<StackOp> = collect_ops(|e| {
e(StackOp::Opcode("OP_FROMALTSTACK".into()));
e(StackOp::Dup);
e(StackOp::Opcode("OP_TOALTSTACK".into()));
if j + 1 > 0 {
e(StackOp::Push(PushValue::Int((1i128 << (j + 1)) as i128)));
e(StackOp::Opcode("OP_DIV".into()));
}
e(StackOp::Push(PushValue::Int(4)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(4) {
e(op);
}
emit_build_adrs(e, layer, SLH_TREE, j + 1, 4, None, HashMode::Stack);
e(StackOp::Swap);
emit_slh_t_raw(e, n, 5);
});
let mut then_branch = vec![
StackOp::Opcode("OP_CAT".into()),
];
then_branch.extend(mk_tweak_hash.iter().cloned());
let mut else_branch = vec![
StackOp::Swap,
StackOp::Opcode("OP_CAT".into()),
];
else_branch.extend(mk_tweak_hash.iter().cloned());
emit(StackOp::If {
then_ops: then_branch,
else_ops: else_branch,
});
}
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Drop);
emit(StackOp::Swap);
emit(StackOp::Drop);
}
fn emit_slh_fors(emit: &mut dyn FnMut(StackOp), p: &SLHCodegenParams) {
let n = p.n;
let a = p.a;
let k = p.k;
emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Opcode("OP_0".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
for i in 0..k {
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Dup);
emit(StackOp::Opcode("OP_TOALTSTACK".into())); emit(StackOp::Swap);
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
let bit_start = i * a;
let byte_start = bit_start / 8;
let bit_offset = bit_start % 8;
let bits_in_first = std::cmp::min(8 - bit_offset, a);
let take = if a > bits_in_first { 2 } else { 1 };
if byte_start > 0 {
emit(StackOp::Push(PushValue::Int(byte_start as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Nip);
}
emit(StackOp::Push(PushValue::Int(take as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Drop);
if take > 1 {
for op in emit_reverse_n(take) {
emit(op);
}
}
emit(StackOp::Push(PushValue::Int(0)));
emit(StackOp::Push(PushValue::Int(1)));
emit(StackOp::Opcode("OP_NUM2BIN".into()));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_BIN2NUM".into()));
let total_bits = take * 8;
let right_shift = total_bits - bit_offset - a;
if right_shift > 0 {
emit(StackOp::Push(PushValue::Int((1i128 << right_shift) as i128)));
emit(StackOp::Opcode("OP_DIV".into()));
}
emit(StackOp::Push(PushValue::Int(1i128 << a)));
emit(StackOp::Opcode("OP_MOD".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit(StackOp::Push(PushValue::Int(n as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Dup);
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
if i > 0 {
emit(StackOp::Push(PushValue::Int((i * (1 << a)) as i128)));
emit(StackOp::Opcode("OP_ADD".into()));
}
emit(StackOp::Push(PushValue::Int(4)));
emit(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(4) {
emit(op);
}
emit_build_adrs(emit, 0, SLH_FORS_TREE, 0, 4, Some(3), HashMode::Stack);
emit(StackOp::Swap);
emit_slh_t_raw(emit, n, 5);
for j in 0..a {
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
emit(StackOp::Push(PushValue::Int(n as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Dup);
emit(StackOp::Opcode("OP_TOALTSTACK".into()));
if j > 0 {
emit(StackOp::Push(PushValue::Int((1i128 << j) as i128)));
emit(StackOp::Opcode("OP_DIV".into()));
}
emit(StackOp::Push(PushValue::Int(2)));
emit(StackOp::Opcode("OP_MOD".into()));
let mk_fors_auth_hash: Vec<StackOp> = collect_ops(|e| {
e(StackOp::Opcode("OP_FROMALTSTACK".into()));
e(StackOp::Dup);
e(StackOp::Opcode("OP_TOALTSTACK".into()));
if j + 1 > 0 {
e(StackOp::Push(PushValue::Int((1i128 << (j + 1)) as i128)));
e(StackOp::Opcode("OP_DIV".into()));
}
let base = i * (1 << (a - j - 1));
if base > 0 {
e(StackOp::Push(PushValue::Int(base as i128)));
e(StackOp::Opcode("OP_ADD".into()));
}
e(StackOp::Push(PushValue::Int(4)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(4) {
e(op);
}
emit_build_adrs(e, 0, SLH_FORS_TREE, j + 1, 4, Some(3), HashMode::Stack);
e(StackOp::Swap);
emit_slh_t_raw(e, n, 5);
});
let mut then_branch = vec![StackOp::Opcode("OP_CAT".into())];
then_branch.extend(mk_fors_auth_hash.iter().cloned());
let mut else_branch = vec![
StackOp::Swap,
StackOp::Opcode("OP_CAT".into()),
];
else_branch.extend(mk_fors_auth_hash.iter().cloned());
emit(StackOp::If {
then_ops: then_branch,
else_ops: else_branch,
});
}
emit(StackOp::Opcode("OP_FROMALTSTACK".into()));
emit(StackOp::Drop);
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Swap);
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_TOALTSTACK".into())); }
emit(StackOp::Drop);
emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Opcode("OP_FROMALTSTACK".into())); emit(StackOp::Drop);
emit_build_adrs(emit, 0, SLH_FORS_ROOTS, 0, 2, Some(1), HashMode::Zero);
emit(StackOp::Swap);
emit_slh_t_raw(emit, n, 4);
}
fn emit_slh_hmsg(emit: &mut dyn FnMut(StackOp), n: usize, out_len: usize) {
let _ = n;
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_SHA256".into()));
let blocks = (out_len + 31) / 32;
if blocks == 1 {
emit(StackOp::Push(PushValue::Bytes(vec![0u8; 4])));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_SHA256".into()));
if out_len < 32 {
emit(StackOp::Push(PushValue::Int(out_len as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Drop);
}
} else {
emit(StackOp::Opcode("OP_0".into())); emit(StackOp::Swap);
for ctr in 0..blocks {
if ctr < blocks - 1 {
emit(StackOp::Dup);
}
let ctr_bytes = vec![
((ctr >> 24) & 0xff) as u8,
((ctr >> 16) & 0xff) as u8,
((ctr >> 8) & 0xff) as u8,
(ctr & 0xff) as u8,
];
emit(StackOp::Push(PushValue::Bytes(ctr_bytes)));
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Opcode("OP_SHA256".into()));
if ctr == blocks - 1 {
let rem = out_len - ctr * 32;
if rem < 32 {
emit(StackOp::Push(PushValue::Int(rem as i128)));
emit(StackOp::Opcode("OP_SPLIT".into()));
emit(StackOp::Drop);
}
}
if ctr < blocks - 1 {
emit(StackOp::Rot);
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_CAT".into()));
emit(StackOp::Swap);
} else {
emit(StackOp::Swap);
emit(StackOp::Opcode("OP_CAT".into()));
}
}
}
}
pub fn emit_verify_slh_dsa(emit: &mut dyn FnMut(StackOp), param_key: &str) {
let p = slh_params(param_key);
let n = p.n;
let d = p.d;
let hp = p.hp;
let k = p.k;
let a = p.a;
let ln = p.len;
let fors_sig_len = k * (1 + a) * n;
let xmss_sig_len = (ln + hp) * n;
let md_len = (k * a + 7) / 8;
let tree_idx_len = (p.h - hp + 7) / 8;
let leaf_idx_len = (hp + 7) / 8;
let digest_len = md_len + tree_idx_len + leaf_idx_len;
let mut t = SLHTracker::new(&["msg", "sig", "pubkey"], emit);
t.to_top("pubkey");
t.push_int("", n as i128);
t.split("pkSeed", "pkRoot");
t.copy_to_top("pkSeed", "_psp");
if 64 - n > 0 {
t.push_bytes("", vec![0u8; 64 - n]);
t.cat("_pkSeedPad");
} else {
t.rename("_pkSeedPad");
}
t.to_top("sig");
t.push_int("", n as i128);
t.split("R", "sigRest");
t.copy_to_top("R", "_R");
t.copy_to_top("pkSeed", "_pks");
t.copy_to_top("pkRoot", "_pkr");
t.copy_to_top("msg", "_msg");
t.raw_block(&["_R", "_pks", "_pkr", "_msg"], "digest", |e| {
emit_slh_hmsg(e, n, digest_len);
});
t.to_top("digest");
t.push_int("", md_len as i128);
t.split("md", "_drest");
t.to_top("_drest");
t.push_int("", tree_idx_len as i128);
t.split("_treeBytes", "_leafBytes");
t.to_top("_treeBytes");
t.raw_block(&["_treeBytes"], "treeIdx", |e| {
if tree_idx_len > 1 {
for op in emit_reverse_n(tree_idx_len) {
e(op);
}
}
e(StackOp::Push(PushValue::Int(0)));
e(StackOp::Push(PushValue::Int(1)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
e(StackOp::Opcode("OP_CAT".into()));
e(StackOp::Opcode("OP_BIN2NUM".into()));
let shift = p.h - hp;
let modulus: i128 = if shift >= 127 {
i128::MAX
} else {
1i128 << shift
};
e(StackOp::Push(PushValue::Int(modulus)));
e(StackOp::Opcode("OP_MOD".into()));
});
t.to_top("_leafBytes");
t.raw_block(&["_leafBytes"], "leafIdx", |e| {
if leaf_idx_len > 1 {
for op in emit_reverse_n(leaf_idx_len) {
e(op);
}
}
e(StackOp::Push(PushValue::Int(0)));
e(StackOp::Push(PushValue::Int(1)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
e(StackOp::Opcode("OP_CAT".into()));
e(StackOp::Opcode("OP_BIN2NUM".into()));
let hp_modulus: i128 = if hp >= 127 {
i128::MAX
} else {
1i128 << hp
};
e(StackOp::Push(PushValue::Int(hp_modulus)));
e(StackOp::Opcode("OP_MOD".into()));
});
t.copy_to_top("treeIdx", "_ti8");
t.raw_block(&["_ti8"], "treeAddr8", |e| {
e(StackOp::Push(PushValue::Int(8)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(8) {
e(op);
}
});
t.copy_to_top("leafIdx", "_li4");
t.raw_block(&["_li4"], "keypair4", |e| {
e(StackOp::Push(PushValue::Int(4)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(4) {
e(op);
}
});
t.to_top("sigRest");
t.push_int("", fors_sig_len as i128);
t.split("forsSig", "htSigRest");
t.copy_to_top("_pkSeedPad", "_psp");
t.copy_to_top("treeAddr8", "_ta");
t.copy_to_top("keypair4", "_kp");
t.to_top("forsSig");
t.to_top("md");
t.raw_block(&["_psp", "_ta", "_kp", "forsSig", "md"], "forsPk", |e| {
emit_slh_fors(e, &p);
e(StackOp::Opcode("OP_TOALTSTACK".into())); e(StackOp::Drop); e(StackOp::Drop); e(StackOp::Drop); e(StackOp::Opcode("OP_FROMALTSTACK".into())); });
for layer in 0..d {
t.to_top("htSigRest");
t.push_int("", xmss_sig_len as i128);
let xsig_name = format!("xsig{}", layer);
t.split(&xsig_name, "htSigRest");
t.to_top(&xsig_name);
t.push_int("", (ln * n) as i128);
let wsig_name = format!("wsig{}", layer);
let auth_name = format!("auth{}", layer);
t.split(&wsig_name, &auth_name);
let cur_msg = if layer == 0 {
"forsPk".to_string()
} else {
format!("root{}", layer - 1)
};
t.copy_to_top("_pkSeedPad", "_psp");
t.copy_to_top("treeAddr8", "_ta");
t.copy_to_top("keypair4", "_kp");
t.to_top(&wsig_name);
t.to_top(&cur_msg);
let wpk_name = format!("wpk{}", layer);
t.raw_block(&["_psp", "_ta", "_kp", &wsig_name, &cur_msg], &wpk_name, |e| {
emit_slh_wots_all(e, &p, layer);
e(StackOp::Opcode("OP_TOALTSTACK".into()));
e(StackOp::Drop); e(StackOp::Drop); e(StackOp::Drop);
e(StackOp::Opcode("OP_FROMALTSTACK".into()));
});
t.copy_to_top("_pkSeedPad", "_psp");
t.copy_to_top("treeAddr8", "_ta");
t.copy_to_top("keypair4", "_kp");
t.to_top("leafIdx");
t.to_top(&auth_name);
t.to_top(&wpk_name);
let root_name = format!("root{}", layer);
t.raw_block(&["_psp", "_ta", "_kp", "leafIdx", &auth_name, &wpk_name], &root_name, |e| {
emit_slh_merkle(e, &p, layer);
e(StackOp::Opcode("OP_TOALTSTACK".into()));
e(StackOp::Drop); e(StackOp::Drop); e(StackOp::Drop);
e(StackOp::Opcode("OP_FROMALTSTACK".into()));
});
if layer < d - 1 {
t.to_top("treeIdx");
t.dup("_tic");
t.raw_block(&["_tic"], "leafIdx", |e| {
e(StackOp::Push(PushValue::Int(1i128 << hp)));
e(StackOp::Opcode("OP_MOD".into()));
});
t.swap();
t.raw_block(&["treeIdx"], "treeIdx", |e| {
e(StackOp::Push(PushValue::Int((1i128 << hp) as i128)));
e(StackOp::Opcode("OP_DIV".into()));
});
t.to_top("treeAddr8");
t.drop();
t.copy_to_top("treeIdx", "_ti8");
t.raw_block(&["_ti8"], "treeAddr8", |e| {
e(StackOp::Push(PushValue::Int(8)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(8) {
e(op);
}
});
t.to_top("keypair4");
t.drop();
t.copy_to_top("leafIdx", "_li4");
t.raw_block(&["_li4"], "keypair4", |e| {
e(StackOp::Push(PushValue::Int(4)));
e(StackOp::Opcode("OP_NUM2BIN".into()));
for op in emit_reverse_n(4) {
e(op);
}
});
}
}
let final_root = format!("root{}", d - 1);
t.to_top(&final_root);
t.to_top("pkRoot");
t.equal("_result");
t.to_top("_result");
t.to_alt();
let leftover = [
"msg", "R", "pkSeed", "htSigRest", "treeIdx", "leafIdx",
"_pkSeedPad", "treeAddr8", "keypair4",
];
for nm in &leftover {
if t.has(nm) {
t.to_top(nm);
t.drop();
}
}
while t.depth() > 0 {
t.drop();
}
t.from_alt("_result");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slh_params_exist() {
for key in &[
"SHA2_128s",
"SHA2_128f",
"SHA2_192s",
"SHA2_192f",
"SHA2_256s",
"SHA2_256f",
] {
let p = slh_params(key);
assert!(p.n > 0);
assert!(p.len > 0);
assert_eq!(p.hp, p.h / p.d);
}
}
#[test]
fn test_adrs_22_bytes() {
let a = slh_adrs(&SLHADRSOpts {
layer: 3,
tree: 42,
adrs_typ: SLH_WOTS_HASH,
keypair: 7,
chain: 5,
hash: 9,
});
assert_eq!(a.len(), 22);
assert_eq!(a[0], 3); assert_eq!(a[9], SLH_WOTS_HASH); }
#[test]
fn test_adrs18_prefix() {
let a18 = slh_adrs18(&SLHADRSOpts {
layer: 1,
adrs_typ: SLH_WOTS_HASH,
chain: 2,
..Default::default()
});
assert_eq!(a18.len(), 18);
}
#[test]
fn test_emit_verify_slh_dsa_produces_ops() {
let mut ops = Vec::new();
emit_verify_slh_dsa(&mut |op| ops.push(op), "SHA2_128s");
assert!(!ops.is_empty(), "should produce stack ops");
}
#[test]
fn test_chain_step_then() {
let ops = slh_chain_step_then(16, 6);
assert!(!ops.is_empty());
}
}