/* gcc Xoodyak.c -o xoodyak.exe */
/* echo "Ark" | ./xoodyak.exe */
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define XOOCYCLE_SPONGE 48
#define FOR(i, len) for ((i) = 0; (i) < (len); (i)++)
typedef uint8_t u8;
typedef uint32_t u32;
typedef size_t size;
typedef enum { xoocycle_up, xoocycle_down } xoocycle_phase;
typedef enum { xoocycle_hash, xoocycle_keyed } xoocycle_mode;
typedef struct
{
u8 sponge[XOOCYCLE_SPONGE];
xoocycle_phase phase;
xoocycle_mode mode;
size absorb;
size squeeze;
} xoocycle;
extern u8 xoocycle_empty[1];
extern void xoocycle_cyclist(xoocycle *cyc, const u8 *k, size k_len,
const u8 *id, size id_len,
const u8 *counter, size counter_len);
extern void xoocycle_absorb(xoocycle *cyc, const u8 *input, size len);
extern void xoocycle_encrypt(xoocycle *cyc, u8 *plain, size len);
extern void xoocycle_decrypt(xoocycle *cyc, u8 *cipher, size len);
extern void xoocycle_squeeze(xoocycle *cyc, u8 *output, size len);
extern void xoocycle_squeeze_key(xoocycle *cyc, u8 *output, size len);
extern void xoocycle_ratchet(xoocycle *cyc);
extern void xoocycle_erase_u8(void *delenda, size len);
extern void xoocycle_erase(xoocycle *cyc);
#define XOOCYCLE_HASH 16
#define XOOCYCLE_KEYIN 44
#define XOOCYCLE_KEYOUT 24
#define XOOCYCLE_RATCHET 16
#define DOMAIN_DEFAULT 0x00
#define DOMAIN_KEY 0x02
#define DOMAIN_ABSORB 0x03
#define DOMAIN_RATCHET 0x10
#define DOMAIN_SQUEEZE 0x40
#define DOMAIN_CRYPT 0x80
#define PERMUTATION xoodoo8
#define ROUNDS 12
#define SPONGE_END ((XOOCYCLE_SPONGE) - 1)
#define ROTR(v, n) (((v) >> (n)) | ((v) << (32 - (n))))
#define SWAP(s, u, v) t = (s)[u], (s)[u] = (s)[v], (s)[v] = t
#define MIN(a, b) (size)((a) < (b) ? (a) : (b))
#define ZERO(i, data, len) \
FOR((i), (len)) { \
(data)[i] = 0; \
}
#define SPLIT(n, len, start, step) \
for ((start) = 0, (step) = MIN((n), (len) - (start)); \
((start) == 0) || ((start) < (len)); \
(start) += (n), (step) = MIN((n), (len) - (start)))
typedef enum { false, true } boolean;
u8 xoocycle_empty[1] = {0};
static void xoodoo32(u32 *s32, u32 rounds)
{
u32 e[4], a, b, c, t, r, i;
u32 k[24] = {120, 52, 576, 160, 384, 22, 112, 60, 832, 144, 320, 24,
88, 56, 960, 208, 288, 20, 96, 44, 896, 240, 416, 18};
FOR(r, rounds) {
FOR(i, 4) {
e[i] = ROTR(s32[i] ^ s32[i + 4] ^ s32[i + 8], 18),
e[i] ^= ROTR(e[i], 9);
}
FOR(i, 12) {
s32[i] ^= e[(i - 1) & 3];
}
SWAP(s32, 7, 4);
SWAP(s32, 7, 5);
SWAP(s32, 7, 6);
s32[0] ^= k[(24 - rounds) + r];
FOR(i, 4) {
a = s32[i], b = s32[i + 4], c = ROTR(s32[i + 8], 21),
s32[i + 8] = ROTR((b & ~a) ^ c, 24),
s32[i + 4] = ROTR((a & ~c) ^ b, 31),
s32[i] ^= c & ~b;
}
SWAP(s32, 8, 10);
SWAP(s32, 9, 11);
}
}
void xoodoo8(u8 *s8, u32 rounds)
{
u32 *s32 = (u32 *)s8;
xoodoo32(s32, rounds);
}
static void up(xoocycle *cyc, u8 *output, size len, u8 domain)
{
size i = 0;
cyc->phase = xoocycle_up;
if (cyc->mode != xoocycle_hash) {
cyc->sponge[SPONGE_END] ^= domain;
}
PERMUTATION(cyc->sponge, ROUNDS);
FOR(i, len) {
output[i] = cyc->sponge[i];
}
}
static void down(xoocycle *cyc, const u8 *input, size len, u8 domain)
{
size i = 0;
cyc->phase = xoocycle_down;
FOR(i, len) {
cyc->sponge[i] ^= input[i];
}
cyc->sponge[len] ^= 0x01;
if (cyc->mode == xoocycle_hash) {
cyc->sponge[SPONGE_END] ^= (domain & 0x01);
} else {
cyc->sponge[SPONGE_END] ^= domain;
}
}
static void squeeze_any(xoocycle *cyc, u8 *output, size len, u8 domain)
{
size first_len = MIN(len, cyc->squeeze);
size subsequent_len = 0;
size aggregate_len = 0;
up(cyc, &output[aggregate_len], first_len, domain);
aggregate_len += first_len;
while (aggregate_len < len) {
down(cyc, (const u8 *)"", 0, DOMAIN_DEFAULT);
subsequent_len = MIN(len - aggregate_len, cyc->squeeze);
up(cyc, &output[aggregate_len], subsequent_len, DOMAIN_DEFAULT);
aggregate_len += subsequent_len;
}
}
static void crypto(xoocycle *cyc, u8 *io, size len, boolean decrypt)
{
size start = 0;
size step = 0;
size i = 0;
u8 up_output[XOOCYCLE_KEYOUT];
u8 down_input[XOOCYCLE_KEYOUT];
ZERO(i, up_output, XOOCYCLE_KEYOUT);
ZERO(i, down_input, XOOCYCLE_KEYOUT);
SPLIT(XOOCYCLE_KEYOUT, len, start, step) {
if (start == 0) {
up(cyc, &up_output[0], step, DOMAIN_CRYPT);
} else {
up(cyc, &up_output[0], step, DOMAIN_DEFAULT);
}
if (decrypt) {
FOR(i, step) {
io[start + i] ^= up_output[i];
down_input[i] = io[start + i];
}
} else {
FOR(i, step) {
down_input[i] = io[start + i];
io[start + i] ^= up_output[i];
}
}
down(cyc, down_input, step, DOMAIN_DEFAULT);
}
}
static void absorb_any(xoocycle *cyc, const u8 *input, size len,
size r, u8 domain)
{
size start = 0;
size step = 0;
SPLIT(r, len, start, step) {
if (cyc->phase != xoocycle_up) {
up(cyc, xoocycle_empty, 0, DOMAIN_DEFAULT);
}
if (start == 0) {
down(cyc, &input[start], step, domain);
} else {
down(cyc, &input[start], step, DOMAIN_DEFAULT);
}
}
}
static void absorb_key(xoocycle *cyc, const u8 *k, size k_len,
const u8 *id, size id_len,
const u8 *counter, size counter_len)
{
size i = 0;
u8 kie[XOOCYCLE_KEYIN];
size kie_len = k_len + id_len + 1;
cyc->mode = xoocycle_keyed;
cyc->absorb = XOOCYCLE_KEYIN;
cyc->squeeze = XOOCYCLE_KEYOUT;
FOR(i, k_len) {
kie[i] = k[i];
}
FOR(i, id_len) {
kie[i + k_len] = id[i];
}
kie[k_len + id_len] = (u8)(id_len & 0xff);
absorb_any(cyc, &kie[0], kie_len, cyc->absorb, DOMAIN_KEY);
if (counter_len > 0) {
absorb_any(cyc, counter, counter_len, 1, DOMAIN_DEFAULT);
}
}
extern void xoocycle_cyclist(xoocycle *cyc, const u8 *k, size k_len,
const u8 *id, size id_len,
const u8 *counter, size counter_len)
{
size i = 0;
cyc->phase = xoocycle_up;
FOR(i, XOOCYCLE_SPONGE) {
cyc->sponge[i] = 0;
}
cyc->mode = xoocycle_hash;
cyc->absorb = XOOCYCLE_HASH;
cyc->squeeze = XOOCYCLE_HASH;
if (k_len > 0) {
absorb_key(cyc, k, k_len, id, id_len, counter, counter_len);
}
}
extern void xoocycle_absorb(xoocycle *cyc, const u8 *input, size len)
{
absorb_any(cyc, input, len, cyc->absorb, DOMAIN_ABSORB);
}
extern void xoocycle_encrypt(xoocycle *cyc, u8 *plain, size len)
{
if (cyc->mode != xoocycle_keyed) {
return;
}
crypto(cyc, plain, len, false);
}
extern void xoocycle_decrypt(xoocycle *cyc, u8 *cipher, size len)
{
if (cyc->mode != xoocycle_keyed) {
return;
}
crypto(cyc, cipher, len, true);
}
extern void xoocycle_squeeze(xoocycle *cyc, u8 *output, size len)
{
squeeze_any(cyc, output, len, DOMAIN_SQUEEZE);
}
extern void xoocycle_squeeze_key(xoocycle *cyc, u8 *output, size len)
{
if (cyc->mode != xoocycle_keyed) {
return;
}
squeeze_any(cyc, output, len, DOMAIN_KEY);
}
extern void xoocycle_ratchet(xoocycle *cyc)
{
size i;
u8 io[XOOCYCLE_RATCHET];
ZERO(i, io, XOOCYCLE_RATCHET);
if (cyc->mode != xoocycle_keyed) {
return;
}
squeeze_any(cyc, io, XOOCYCLE_RATCHET, DOMAIN_RATCHET);
absorb_any(cyc, io, XOOCYCLE_RATCHET, cyc->absorb, DOMAIN_DEFAULT);
}
extern void xoocycle_erase_u8(void *delenda, size len)
{
volatile u8 *volatile ephemeral = (volatile u8 *volatile)delenda;
size i = (size)0U;
while (i < len) {
ephemeral[i++] = 0U;
}
}
extern void xoocycle_erase(xoocycle *cyc)
{
xoocycle_erase_u8(cyc->sponge, XOOCYCLE_SPONGE);
}
#define IO 4096
#define HASH 32
static void print8(u8 *bytes, size len)
{
size i;
FOR(i, len) {
printf("%02x", (unsigned int)bytes[i]);
}
printf("\n");
}
int main(void)
{
u8 io[IO];
int len = 0;
size i = 0;
xoocycle cyc;
FOR(i, IO) {
io[i] = 0;
}
xoocycle_cyclist(&cyc, xoocycle_empty, 0, xoocycle_empty, 0,
xoocycle_empty, 0);
while (1) {
len = read(STDIN_FILENO, io, IO);
if (len < 0) {
fprintf(stderr, "error\n");
exit(EXIT_FAILURE);
}
if (len == 0) {
break;
}
xoocycle_absorb(&cyc, io, len);
}
xoocycle_squeeze(&cyc, io, HASH);
print8(io, HASH);
xoocycle_erase(&cyc);
return 0;
}