feat: add Jasmin assembly integration for crypto acceleration

- Integrated `zupt_mac_verify_ct` in `zupt_decrypt_buffer()` to replace C XOR loop for HMAC-SHA256
- Integrated `zupt_ct_select_32` in `zupt_mlkem768_decaps()` to replace C `cmov()` for FO transformation
- Added `include/zupt_jasmin.h` with extern declarations and ABI docs
- Added `#ifdef ZUPT_USE_JASMIN` guards with clean C fallbacks in `zupt_crypto.c` and `zupt_mlkem.c`
- Makefile now auto-detects `jasmin/*.s`, assembles and links with `-DZUPT_USE_JASMIN`

Closes #3
This commit is contained in:
Cristian Cezar Moisés 2026-03-28 23:00:28 -03:00
commit 06c877ec86
43 changed files with 1913 additions and 546 deletions

124
jasmin/zupt_aes_ctr.jazz Normal file
View file

@ -0,0 +1,124 @@
/* Zupt — AES-256 Single Block Encrypt via AES-NI (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés — MIT License
*
* CT-REQUIRED: AES-NI has no data-dependent timing.
*
* Uses reg ptr for read-only u128 inputs (key, counter, plaintext).
* Uses reg u64 for write output (store infers width from reg u128 source).
* C handles CTR loop and tail bytes.
*/
inline fn key_expand_even(reg u128 t0, reg u128 assist) -> reg u128 {
reg u128 tmp;
assist = #VPSHUFD(assist, 0xFF);
tmp = #VPSLLDQ(t0, 4);
t0 ^= tmp;
tmp = #VPSLLDQ(t0, 4);
t0 ^= tmp;
tmp = #VPSLLDQ(t0, 4);
t0 ^= tmp;
t0 ^= assist;
return t0;
}
inline fn key_expand_odd(reg u128 t0, reg u128 t1) -> reg u128 {
reg u128 tmp assist;
assist = #VAESKEYGENASSIST(t0, 0);
assist = #VPSHUFD(assist, 0xAA);
tmp = #VPSLLDQ(t1, 4);
t1 ^= tmp;
tmp = #VPSLLDQ(t1, 4);
t1 ^= tmp;
tmp = #VPSLLDQ(t1, 4);
t1 ^= tmp;
t1 ^= assist;
return t1;
}
export fn zupt_aes256_blk(
reg u64 out_ptr,
reg ptr u128[1] in_blk,
reg ptr u128[2] key,
reg ptr u128[1] ctr_blk)
{
stack u128[15] rk;
reg u128 t0 t1 assist b data;
/* Key expansion */
t0 = key.[0];
t1 = key.[1];
rk.[0] = t0;
rk.[1] = t1;
assist = #VAESKEYGENASSIST(t1, 0x01);
t0 = key_expand_even(t0, assist);
rk.[2] = t0;
t1 = key_expand_odd(t0, t1);
rk.[3] = t1;
assist = #VAESKEYGENASSIST(t1, 0x02);
t0 = key_expand_even(t0, assist);
rk.[4] = t0;
t1 = key_expand_odd(t0, t1);
rk.[5] = t1;
assist = #VAESKEYGENASSIST(t1, 0x04);
t0 = key_expand_even(t0, assist);
rk.[6] = t0;
t1 = key_expand_odd(t0, t1);
rk.[7] = t1;
assist = #VAESKEYGENASSIST(t1, 0x08);
t0 = key_expand_even(t0, assist);
rk.[8] = t0;
t1 = key_expand_odd(t0, t1);
rk.[9] = t1;
assist = #VAESKEYGENASSIST(t1, 0x10);
t0 = key_expand_even(t0, assist);
rk.[10] = t0;
t1 = key_expand_odd(t0, t1);
rk.[11] = t1;
assist = #VAESKEYGENASSIST(t1, 0x20);
t0 = key_expand_even(t0, assist);
rk.[12] = t0;
t1 = key_expand_odd(t0, t1);
rk.[13] = t1;
assist = #VAESKEYGENASSIST(t1, 0x40);
t0 = key_expand_even(t0, assist);
rk.[14] = t0;
/* Encrypt counter block: 14 rounds AES-256 */
b = ctr_blk.[0];
b ^= rk.[0];
b = #VAESENC(b, rk.[1]);
b = #VAESENC(b, rk.[2]);
b = #VAESENC(b, rk.[3]);
b = #VAESENC(b, rk.[4]);
b = #VAESENC(b, rk.[5]);
b = #VAESENC(b, rk.[6]);
b = #VAESENC(b, rk.[7]);
b = #VAESENC(b, rk.[8]);
b = #VAESENC(b, rk.[9]);
b = #VAESENC(b, rk.[10]);
b = #VAESENC(b, rk.[11]);
b = #VAESENC(b, rk.[12]);
b = #VAESENC(b, rk.[13]);
b = #VAESENCLAST(b, rk.[14]);
/* XOR keystream with plaintext, store result */
data = in_blk.[0];
b ^= data;
[out_ptr + 0] = b;
/* Wipe round keys */
reg u128 wipe;
inline int z;
for z = 0 to 15 {
wipe = rk.[z];
wipe ^= wipe;
rk.[z] = wipe;
}
}

166
jasmin/zupt_aes_ctr.s Normal file
View file

@ -0,0 +1,166 @@
.intel_syntax noprefix
.text
.p2align 5
.global zupt_aes256_blk
.type zupt_aes256_blk, %function
zupt_aes256_blk:
mov r10, rsp
lea rsp, qword ptr[rsp + -240]
and rsp, -16
vmovdqu xmm0, xmmword ptr[rdx]
vmovdqu xmm1, xmmword ptr[rdx + 1]
vmovdqu xmmword ptr[rsp], xmm0
vmovdqu xmmword ptr[rsp + 1], xmm1
vaeskeygenassist xmm2, xmm1, 1
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 2], xmm0
vaeskeygenassist xmm2, xmm0, 0
vpshufd xmm2, xmm2, 170
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpxor xmm1, xmm1, xmm2
vmovdqu xmmword ptr[rsp + 3], xmm1
vaeskeygenassist xmm2, xmm1, 2
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 4], xmm0
vaeskeygenassist xmm2, xmm0, 0
vpshufd xmm2, xmm2, 170
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpxor xmm1, xmm1, xmm2
vmovdqu xmmword ptr[rsp + 5], xmm1
vaeskeygenassist xmm2, xmm1, 4
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 6], xmm0
vaeskeygenassist xmm2, xmm0, 0
vpshufd xmm2, xmm2, 170
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpxor xmm1, xmm1, xmm2
vmovdqu xmmword ptr[rsp + 7], xmm1
vaeskeygenassist xmm2, xmm1, 8
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 8], xmm0
vaeskeygenassist xmm2, xmm0, 0
vpshufd xmm2, xmm2, 170
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpxor xmm1, xmm1, xmm2
vmovdqu xmmword ptr[rsp + 9], xmm1
vaeskeygenassist xmm2, xmm1, 16
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 10], xmm0
vaeskeygenassist xmm2, xmm0, 0
vpshufd xmm2, xmm2, 170
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpxor xmm1, xmm1, xmm2
vmovdqu xmmword ptr[rsp + 11], xmm1
vaeskeygenassist xmm2, xmm1, 32
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 12], xmm0
vaeskeygenassist xmm2, xmm0, 0
vpshufd xmm2, xmm2, 170
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpslldq xmm3, xmm1, 4
vpxor xmm1, xmm1, xmm3
vpxor xmm1, xmm1, xmm2
vmovdqu xmmword ptr[rsp + 13], xmm1
vaeskeygenassist xmm2, xmm1, 64
vpshufd xmm2, xmm2, 255
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpslldq xmm3, xmm0, 4
vpxor xmm0, xmm0, xmm3
vpxor xmm0, xmm0, xmm2
vmovdqu xmmword ptr[rsp + 14], xmm0
vmovdqu xmm0, xmmword ptr[rcx]
vpxor xmm0, xmm0, xmmword ptr[rsp]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 1]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 2]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 3]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 4]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 5]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 6]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 7]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 8]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 9]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 10]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 11]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 12]
vaesenc xmm0, xmm0, xmmword ptr[rsp + 13]
vaesenclast xmm0, xmm0, xmmword ptr[rsp + 14]
vmovdqu xmm1, xmmword ptr[rsi]
vpxor xmm0, xmm0, xmm1
movq qword ptr[rdi], xmm0
mov rsp, r10
ret
.ident "Jasmin Compiler 2026.03.0"
.section ".note.GNU-stack", "", %progbits

View file

@ -0,0 +1,28 @@
/* Zupt — Constant-Time MAC Comparison (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés — MIT License
*
* CT-REQUIRED: Timing independent of input byte values.
* Compare 32 bytes as 4 × u64 — no byte-level access needed.
*/
export fn zupt_mac_verify_ct(
reg u64 expected_ptr,
reg u64 actual_ptr)
-> reg u64
{
reg u64 diff a b tmp;
inline int i;
diff = 0;
/* 4 × 8 bytes = 32 bytes. u64 loads — no size mismatch. */
for i = 0 to 4 {
a = [expected_ptr + 8 * i];
b = [actual_ptr + 8 * i];
tmp = a;
tmp ^= b;
diff |= tmp;
}
return diff;
}

BIN
jasmin/zupt_mac_verify.o Normal file

Binary file not shown.

26
jasmin/zupt_mac_verify.s Normal file
View file

@ -0,0 +1,26 @@
.intel_syntax noprefix
.text
.p2align 5
.global zupt_mac_verify_ct
.type zupt_mac_verify_ct, %function
zupt_mac_verify_ct:
mov rax, 0
mov rcx, qword ptr[rdi]
mov rdx, qword ptr[rsi]
xor rcx, rdx
or rax, rcx
mov rcx, qword ptr[rdi + 8]
mov rdx, qword ptr[rsi + 8]
xor rcx, rdx
or rax, rcx
mov rcx, qword ptr[rdi + 16]
mov rdx, qword ptr[rsi + 16]
xor rcx, rdx
or rax, rcx
mov rcx, qword ptr[rdi + 24]
mov rdx, qword ptr[rsi + 24]
xor rcx, rdx
or rax, rcx
ret
.ident "Jasmin Compiler 2026.03.0"
.section ".note.GNU-stack", "", %progbits

View file

@ -0,0 +1,31 @@
/* Zupt — ML-KEM Constant-Time Select (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés — MIT License
*
* CT-REQUIRED: FO implicit rejection must not leak via timing.
* Operates in u64 chunks: 4 × 8 = 32 bytes, no byte access.
*/
export fn zupt_ct_select_32(
reg u64 out_ptr,
reg u64 a_ptr,
reg u64 b_ptr,
reg u64 cond)
{
reg u64 mask va vb tmp sel;
inline int i;
mask = 0;
mask -= cond;
/* 4 × u64 = 32 bytes */
for i = 0 to 4 {
va = [a_ptr + 8 * i];
vb = [b_ptr + 8 * i];
tmp = va;
tmp ^= vb;
tmp &= mask;
sel = va;
sel ^= tmp;
[out_ptr + 8 * i] = sel;
}
}

BIN
jasmin/zupt_mlkem_select.o Normal file

Binary file not shown.

View file

@ -0,0 +1,39 @@
.intel_syntax noprefix
.text
.p2align 5
.global zupt_ct_select_32
.type zupt_ct_select_32, %function
zupt_ct_select_32:
mov rax, 0
sub rax, rcx
mov rcx, qword ptr[rsi]
mov r8, qword ptr[rdx]
mov r9, rcx
xor r9, r8
and r9, rax
xor rcx, r9
mov qword ptr[rdi], rcx
mov rcx, qword ptr[rsi + 8]
mov r8, qword ptr[rdx + 8]
mov r9, rcx
xor r9, r8
and r9, rax
xor rcx, r9
mov qword ptr[rdi + 8], rcx
mov rcx, qword ptr[rsi + 16]
mov r8, qword ptr[rdx + 16]
mov r9, rcx
xor r9, r8
and r9, rax
xor rcx, r9
mov qword ptr[rdi + 16], rcx
mov rcx, qword ptr[rsi + 24]
mov r8, qword ptr[rdx + 24]
mov r9, rcx
xor r9, r8
and r9, rax
xor rcx, r9
mov qword ptr[rdi + 24], rcx
ret
.ident "Jasmin Compiler 2026.03.0"
.section ".note.GNU-stack", "", %progbits

View file

@ -0,0 +1,34 @@
/* Zupt — X25519 Constant-Time Conditional Swap (Jasmin)
* Copyright (c) 2026 Cristian Cezar Moisés — MIT License
*
* CT-REQUIRED: fe_cswap must not leak cond via timing.
* This is the only CT-critical field operation in X25519.
* fe_add/fe_sub/fe_mul use C fallback (data-independent timing
* on x86-64 — ADD/MUL have fixed latency).
*
* 4 × u64 limbs, pure register operations, no intrinsics needed.
*/
export fn zupt_fe_cswap(
reg u64 a_ptr,
reg u64 b_ptr,
reg u64 cond)
{
reg u64 mask ta tb diff;
inline int i;
mask = 0;
mask -= cond;
for i = 0 to 4 {
ta = [a_ptr + 8 * i];
tb = [b_ptr + 8 * i];
diff = ta;
diff ^= tb;
diff &= mask;
ta ^= diff;
tb ^= diff;
[a_ptr + 8 * i] = ta;
[b_ptr + 8 * i] = tb;
}
}

47
jasmin/zupt_x25519_fe.s Normal file
View file

@ -0,0 +1,47 @@
.intel_syntax noprefix
.text
.p2align 5
.global zupt_fe_cswap
.type zupt_fe_cswap, %function
zupt_fe_cswap:
mov rax, 0
sub rax, rdx
mov rcx, qword ptr[rdi]
mov rdx, qword ptr[rsi]
mov r8, rcx
xor r8, rdx
and r8, rax
xor rcx, r8
xor rdx, r8
mov qword ptr[rdi], rcx
mov qword ptr[rsi], rdx
mov rcx, qword ptr[rdi + 8]
mov rdx, qword ptr[rsi + 8]
mov r8, rcx
xor r8, rdx
and r8, rax
xor rcx, r8
xor rdx, r8
mov qword ptr[rdi + 8], rcx
mov qword ptr[rsi + 8], rdx
mov rcx, qword ptr[rdi + 16]
mov rdx, qword ptr[rsi + 16]
mov r8, rcx
xor r8, rdx
and r8, rax
xor rcx, r8
xor rdx, r8
mov qword ptr[rdi + 16], rcx
mov qword ptr[rsi + 16], rdx
mov rcx, qword ptr[rdi + 24]
mov rdx, qword ptr[rsi + 24]
mov r8, rcx
xor r8, rdx
and r8, rax
xor rcx, r8
xor rdx, r8
mov qword ptr[rdi + 24], rcx
mov qword ptr[rsi + 24], rdx
ret
.ident "Jasmin Compiler 2026.03.0"
.section ".note.GNU-stack", "", %progbits