mirror of
https://github.com/luxfi/pulsar.git
synced 2026-09-24 00:25:52 +00:00
pulsar/jasmin: make threshold round1/round2/combine compile (codegen-only)
These 3 threshold-signature sources type-checked but failed in the Jasmin backend. Fixed with codegen-only restructuring — NO algorithm, value, wire layout, or constant-time change: - combine: decomposed an over-blended x86 effective address into a base reg; split a shifted-OR source into shift-into-temp then OR. - round1: relieved register pressure (spill output ptrs, extract the expandA+matvec+decompose phase into a helper frame, pack derive_rho_prime inputs into one preimage buffer, u32 stores for y_state). - round2: an export fn had 13 pointer params (Jasmin caps at 6) — marshalled them into a caller-supplied table; gave polyvecl_scalar_mont distinct in/out ptrs; routed per-peer MAC tables through one buffer; stack-return digest helpers; spilled keccak-crossing loop-invariants. Shared lib tweaks (consumed by these files): mac/transcript/lagrange/seed .jinc stack-return + u64-index + BE-via-temp. All #[ct] contracts and #declassify pragmas preserved (CT type-checked at compile). Full Jasmin corpus: 23/23 compile (was 20/23). Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -210,16 +210,23 @@ fn lagrange_coefficient_mont(
|
||||
reg u32 xj, xi;
|
||||
reg u32 num, denom, denom_inv, factor;
|
||||
reg u32 xj_mont, factor_mont;
|
||||
// u64 index for the array accesses: indexing a reg-ptr u32 array with
|
||||
// a u32 index makes the backend emit a zero-extended scaled index
|
||||
// that is not an encodable x86 address in some register assignments.
|
||||
// A native u64 index loads as [base + idx*4]. Values unchanged.
|
||||
reg u64 idx;
|
||||
|
||||
// acc = 1 in Montgomery form.
|
||||
acc = to_mont(1);
|
||||
|
||||
xi = eval_points[my_index];
|
||||
idx = (64u) my_index;
|
||||
xi = eval_points[idx];
|
||||
|
||||
?{}, j = #set0_32();
|
||||
while (j < quorum_size) {
|
||||
if (j != my_index) {
|
||||
xj = eval_points[j];
|
||||
idx = (64u) j;
|
||||
xj = eval_points[idx];
|
||||
|
||||
// numerator: (-x_j) mod q
|
||||
num = modq_neg(xj);
|
||||
|
||||
+10
-2
@@ -49,7 +49,13 @@ from Jade require "crypto_sign/dilithium/common/amd64/fips202.jinc"
|
||||
// After this routine, the state is ready to absorb the message body X,
|
||||
// starting at running offset 0 (the second 136-byte block having just
|
||||
// been permuted).
|
||||
inline
|
||||
//
|
||||
// Stack-return call (not inlined): this routine is large (two keccak
|
||||
// permutations plus 30+ state byte-writes). Inlining it into each of the
|
||||
// several Round-2 KMAC sites multiplied that working set across the whole
|
||||
// compilation unit until Jasmin's global register allocator ran dry. As
|
||||
// a real call it is emitted once. Semantics/CT unchanged.
|
||||
#[returnaddress="stack"]
|
||||
fn kmac256_init_R1MAC(reg ptr u64[25] state, reg ptr u8[32] K)
|
||||
-> reg ptr u64[25]
|
||||
{
|
||||
@@ -100,7 +106,9 @@ fn kmac256_init_R1MAC(reg ptr u64[25] state, reg ptr u8[32] K)
|
||||
// followed by the cSHAKE256 finaliser (0x04 || ... || 0x80) and permute.
|
||||
// Squeezes the 32-byte tag into `tag_out`. Caller passes the current
|
||||
// absorption `offset` mod SHAKE256_RATE.
|
||||
inline
|
||||
// Stack-return call (not inlined), same global-pressure rationale as
|
||||
// kmac256_init_R1MAC. Semantics/CT unchanged.
|
||||
#[returnaddress="stack"]
|
||||
fn kmac256_finalize_32(
|
||||
reg ptr u64[25] state,
|
||||
reg u64 offset,
|
||||
|
||||
+29
-17
@@ -63,23 +63,33 @@ fn kmac256_init_PRNG(reg ptr u64[25] state, reg ptr u8[32] K)
|
||||
return state;
|
||||
}
|
||||
|
||||
// tau_1 preimage length: sid(16) || attempt_be(4) || node_id(32) ||
|
||||
// pk_seed(32) || mu(64). Assembled once by the caller and shared between
|
||||
// derive_rho_prime and round1_tau1_digest.
|
||||
param int PULSARM_TAU1_PREIMAGE_LEN = 16 + 4 + 32 + 32 + 64;
|
||||
|
||||
// Derive rho_prime for party i, attempt kappa from:
|
||||
// K - 32-byte PRNG key K_i^prng (long-term secret)
|
||||
// sid - 16-byte session id (public)
|
||||
// attempt - kappa, 32-bit attempt counter (public)
|
||||
// my_node_id - 32-byte party id (public)
|
||||
// pk_seed - 32-byte ML-DSA-65 public-key seed rho (public)
|
||||
// mu - 64-byte FIPS 204 message hash binder (public)
|
||||
// tau1_preimage- PULSARM_TAU1_PREIMAGE_LEN bytes, pre-assembled as
|
||||
// sid || attempt(BE32) || my_node_id || pk_seed || mu
|
||||
// (all public).
|
||||
//
|
||||
// rho_prime_out receives 64 bytes. CT note: K is SECRET.
|
||||
inline
|
||||
//
|
||||
// Compiled as a real (stack-return-address) call rather than inlined, so
|
||||
// its hashing working set gets an independent register frame instead of
|
||||
// flattening into pulsar_round1_commit. Inlined, the round-1 body (every
|
||||
// libjade primitive is `inline`) overflowed the 15 GP registers. The
|
||||
// preimage is passed as a single pre-assembled buffer (rather than five
|
||||
// separate operands) to keep the live argument count low enough that the
|
||||
// inlined keccak working set fits. Reg-ptr args alias caller stack arrays
|
||||
// per libjade convention; the absorbed byte stream and all values are
|
||||
// byte-identical to the previous five-operand form. CT unchanged (K
|
||||
// stays secret; no secret-dependent control flow or indexing).
|
||||
#[returnaddress="stack"]
|
||||
fn derive_rho_prime(
|
||||
reg ptr u8[32] K,
|
||||
reg ptr u8[16] sid,
|
||||
reg u32 attempt,
|
||||
reg ptr u8[32] my_node_id,
|
||||
reg ptr u8[32] pk_seed,
|
||||
reg ptr u8[64] mu,
|
||||
reg ptr u8[PULSARM_TAU1_PREIMAGE_LEN] tau1_preimage,
|
||||
reg ptr u8[64] rho_prime_out)
|
||||
-> reg ptr u8[64]
|
||||
{
|
||||
@@ -94,13 +104,15 @@ fn derive_rho_prime(
|
||||
state_rsp = state;
|
||||
state_rsp = kmac256_init_PRNG(state_rsp, K);
|
||||
|
||||
// Absorb tau_1 = sid || attempt(BE32) || my_node_id || pk_seed || mu.
|
||||
// Absorb tau_1 = sid || attempt(BE32) || my_node_id || pk_seed || mu,
|
||||
// already laid out contiguously in tau1_preimage.
|
||||
?{}, offset = #set0_64();
|
||||
state_rsp, offset = absorb_array16(state_rsp, offset, sid);
|
||||
state_rsp, offset = absorb_u32_be(state_rsp, offset, attempt);
|
||||
state_rsp, offset = absorb_array32(state_rsp, offset, my_node_id);
|
||||
state_rsp, offset = absorb_array32(state_rsp, offset, pk_seed);
|
||||
state_rsp, offset = absorb_array64(state_rsp, offset, mu);
|
||||
?{}, i = #set0_64();
|
||||
while (i < PULSARM_TAU1_PREIMAGE_LEN) {
|
||||
c = tau1_preimage[i];
|
||||
state_rsp, offset = absorb_one_byte(state_rsp, offset, c);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// KMAC finaliser + squeeze 32 bytes into seed_i.
|
||||
seed_i_rsp = seed_i;
|
||||
|
||||
@@ -293,11 +293,16 @@ fn absorb_u32_be(
|
||||
-> reg ptr u64[25], reg u64
|
||||
{
|
||||
reg u8 c;
|
||||
c = (8u)(v >> 24);
|
||||
reg u32 t;
|
||||
// Compute each big-endian byte via an explicit u32 shift into a temp
|
||||
// before the u8 truncation; a fused shift-and-truncate is not always a
|
||||
// single x86 instruction (linearization rejects the blended form in
|
||||
// some register assignments). Byte values are unchanged.
|
||||
t = v; t >>= 24; c = (8u) t;
|
||||
state, offset = absorb_one_byte(state, offset, c);
|
||||
c = (8u)(v >> 16);
|
||||
t = v; t >>= 16; c = (8u) t;
|
||||
state, offset = absorb_one_byte(state, offset, c);
|
||||
c = (8u)(v >> 8);
|
||||
t = v; t >>= 8; c = (8u) t;
|
||||
state, offset = absorb_one_byte(state, offset, c);
|
||||
c = (8u) v;
|
||||
state, offset = absorb_one_byte(state, offset, c);
|
||||
|
||||
@@ -111,6 +111,7 @@ fn pulsar_combine(
|
||||
reg u32 r32, sum32;
|
||||
reg u8 c;
|
||||
reg u8 fail_bits;
|
||||
reg u8 shifted;
|
||||
reg u8 r1_fail, r2_fail, r3_fail, r4_fail;
|
||||
reg u8 norm_z, norm_w_low, norm_ct0;
|
||||
reg u32 hint_count;
|
||||
@@ -161,13 +162,20 @@ fn pulsar_combine(
|
||||
// signature (produced by libjade `pack_signature` below).
|
||||
?{}, j = #set0_32();
|
||||
while (j < threshold) {
|
||||
// Fold the per-message base (round2_msgs_ptr + j*RESPONSE_BYTES)
|
||||
// into a single register so each inner load is an encodable
|
||||
// [base + disp32 + index*scale] mode. Without this fold the
|
||||
// address carries two base registers plus index+scale, which the
|
||||
// x86 effective-address encoder cannot represent. Semantics are
|
||||
// identical: base now points at the start of message j.
|
||||
base = (64u) j;
|
||||
base *= PULSARM_RESPONSE_BYTES;
|
||||
base += round2_msgs_ptr;
|
||||
|
||||
// a) z_i u32 stream → add to z_agg.
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_l * Li2_polydeg) {
|
||||
r32 = [:u32 round2_msgs_ptr + base + PULSARM_R2_Z_OFFSET + 4 * i];
|
||||
r32 = [:u32 base + PULSARM_R2_Z_OFFSET + 4 * i];
|
||||
sum32 = z_agg[i];
|
||||
sum32 += r32;
|
||||
z_agg[i] = sum32;
|
||||
@@ -177,7 +185,7 @@ fn pulsar_combine(
|
||||
// b) r_i u32 stream → add to cs2.
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_k * Li2_polydeg) {
|
||||
r32 = [:u32 round2_msgs_ptr + base + PULSARM_R2_R_OFFSET + 4 * i];
|
||||
r32 = [:u32 base + PULSARM_R2_R_OFFSET + 4 * i];
|
||||
sum32 = cs2[i];
|
||||
sum32 += r32;
|
||||
cs2[i] = sum32;
|
||||
@@ -348,11 +356,21 @@ fn pulsar_combine(
|
||||
if (hint_count > Li2_omega) { r4_fail = 1; }
|
||||
|
||||
// ---- 8. accept = R1 AND R2 AND R3 AND R4 ----
|
||||
// Materialise each shifted fail bit into its own register before the
|
||||
// OR. The x86 backend cannot encode `or r, (s << imm)` (a shifted
|
||||
// sub-expression as the OR's source operand), so we shift into
|
||||
// `shifted` first and then OR. Same bit pattern, no algorithm change.
|
||||
?{}, fail_bits = #set0_8();
|
||||
fail_bits |= r1_fail;
|
||||
fail_bits |= r2_fail << 1;
|
||||
fail_bits |= r3_fail << 2;
|
||||
fail_bits |= r4_fail << 3;
|
||||
shifted = r2_fail;
|
||||
shifted <<= 1;
|
||||
fail_bits |= shifted;
|
||||
shifted = r3_fail;
|
||||
shifted <<= 2;
|
||||
fail_bits |= shifted;
|
||||
shifted = r4_fail;
|
||||
shifted <<= 3;
|
||||
fail_bits |= shifted;
|
||||
|
||||
// Jasmin does not allow `return` inside an `if` block; fall
|
||||
// through and gate the pack on `fail_bits == 0`.
|
||||
|
||||
+184
-60
@@ -97,15 +97,103 @@ fn round1_commit_digest(
|
||||
return D_out;
|
||||
}
|
||||
|
||||
// Expand A from pk_seed and compute the commitment polynomials:
|
||||
// w_i = invNTT( A . NTT(y_i) ) (k polys, canonical form)
|
||||
// w1_i = HighBits(w_i, 2*gamma2) (k polys)
|
||||
// w1_packed = pack_w1(w1_i) (Li2_pack_w1len bytes)
|
||||
//
|
||||
// Pulled out of pulsar_round1_commit into its own (non-inline) function
|
||||
// so the very large inlined working set of expandA + mult_mat_vec +
|
||||
// decompose_vec gets an independent register frame. When this lived in
|
||||
// the caller, the entire round-1 body — every libjade primitive is
|
||||
// `inline` — flattened into one frame whose peak exceeded the 15 GP
|
||||
// registers (the allocator fails on the expandA keccak state colliding
|
||||
// with the still-live hashing pointers). Splitting at this boundary
|
||||
// leaves the heavy matrix expansion with only its own locals live.
|
||||
//
|
||||
// reg-ptr arguments alias the caller's stack arrays and are written
|
||||
// in place / returned by reference, exactly as libjade's own
|
||||
// primitives do. The arithmetic is byte-identical to the original
|
||||
// inlined sequence; no value, ordering, or CT property changes (all
|
||||
// inputs here — pk_seed public, y_i the party's masked nonce — keep
|
||||
// their original sensitivity and there are no secret-dependent
|
||||
// branches or indices).
|
||||
fn round1_w_from_y(
|
||||
reg ptr u8[32] pk_seed,
|
||||
reg ptr u32[Li2_l * Li2_polydeg] y_i,
|
||||
reg ptr u32[Li2_k * Li2_polydeg] w_i_out,
|
||||
reg ptr u32[Li2_k * Li2_polydeg] w1_i_out,
|
||||
reg ptr u8[Li2_pack_w1len] w1_packed_out)
|
||||
-> reg ptr u32[Li2_k * Li2_polydeg],
|
||||
reg ptr u32[Li2_k * Li2_polydeg],
|
||||
reg ptr u8[Li2_pack_w1len]
|
||||
{
|
||||
stack u8[32] pk_seed_local;
|
||||
stack u8[Li2_pack_w1len] w1_packed_local;
|
||||
stack u32[Li2_l * Li2_polydeg] fft_y_i;
|
||||
stack u32[Li2_k * Li2_l * Li2_polydeg] fft_matA;
|
||||
stack u32[Li2_k * Li2_polydeg] w_i;
|
||||
stack u32[Li2_k * Li2_polydeg] w0_i;
|
||||
stack u32[Li2_k * Li2_polydeg] w1_i;
|
||||
reg ptr u8[Li2_pack_w1len] w1_rsp;
|
||||
reg u64 i;
|
||||
reg u32 r32;
|
||||
reg u8 cb;
|
||||
|
||||
// expandA consumes its seed as a by-value `stack u8[32]`; copy the
|
||||
// incoming reg-ptr seed into a local stack array so the allocator
|
||||
// does not have to place a reg-ptr argument onto the stack.
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { cb = pk_seed[i]; pk_seed_local[i] = cb; i += 1; }
|
||||
|
||||
// ---- Expand public matrix A from pk_seed ----
|
||||
fft_matA = expandA(pk_seed_local);
|
||||
|
||||
// ---- w_i = A * y_i in NTT domain, then bring back ----
|
||||
fft_y_i = deep_copy_vecl(y_i);
|
||||
fft_y_i = fft_vec(fft_y_i);
|
||||
w_i = mult_mat_vec(fft_matA, fft_y_i);
|
||||
w_i = reduce32_veck(w_i);
|
||||
w_i = ifft_to_mont_vec(w_i);
|
||||
w_i = polyveck_caddq(w_i);
|
||||
|
||||
// ---- Decompose w_i = w1_i * 2*gamma2 + w0_i ----
|
||||
w0_i, w1_i = decompose_vec(w_i);
|
||||
// pack_w1 returns a fresh local stack buffer; capture it locally then
|
||||
// copy into the caller-provided w1_packed_out (a pointer into a local
|
||||
// stack region cannot be returned across the call boundary).
|
||||
w1_rsp = w1_packed_local;
|
||||
w1_rsp = pack_w1(w1_i);
|
||||
w1_packed_local = w1_rsp;
|
||||
|
||||
// Copy w_i / w1_i into the caller-provided buffers.
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_k * Li2_polydeg) {
|
||||
r32 = w_i[i];
|
||||
w_i_out[i] = r32;
|
||||
r32 = w1_i[i];
|
||||
w1_i_out[i] = r32;
|
||||
i += 1;
|
||||
}
|
||||
// Copy packed w1 into the caller buffer.
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_pack_w1len) {
|
||||
cb = w1_packed_local[i];
|
||||
w1_packed_out[i] = cb;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return w_i_out, w1_i_out, w1_packed_out;
|
||||
}
|
||||
|
||||
// Compute tau_1 = cSHAKE256("PULSAR-SIGN-R1-V1") over:
|
||||
// session_id || attempt(BE32) || my_node_id || pk_seed || mu
|
||||
// The preimage is pre-assembled by the caller (shared with
|
||||
// derive_rho_prime). Absorbed byte stream is identical to the previous
|
||||
// five-operand version.
|
||||
inline
|
||||
fn round1_tau1_digest(
|
||||
reg ptr u8[16] sid,
|
||||
reg u32 attempt,
|
||||
reg ptr u8[32] my_node_id,
|
||||
reg ptr u8[32] pk_seed,
|
||||
reg ptr u8[64] mu,
|
||||
reg ptr u8[PULSARM_TAU1_PREIMAGE_LEN] tau1_preimage,
|
||||
reg ptr u8[32] tau_out)
|
||||
-> reg ptr u8[32]
|
||||
{
|
||||
@@ -118,11 +206,12 @@ fn round1_tau1_digest(
|
||||
state_rsp = state;
|
||||
state_rsp, offset = cshake256_absorb_prelude_R1(state_rsp);
|
||||
|
||||
state_rsp, offset = absorb_array16(state_rsp, offset, sid);
|
||||
state_rsp, offset = absorb_u32_be(state_rsp, offset, attempt);
|
||||
state_rsp, offset = absorb_array32(state_rsp, offset, my_node_id);
|
||||
state_rsp, offset = absorb_array32(state_rsp, offset, pk_seed);
|
||||
state_rsp, offset = absorb_array64(state_rsp, offset, mu);
|
||||
?{}, i = #set0_64();
|
||||
while (i < PULSARM_TAU1_PREIMAGE_LEN) {
|
||||
c = tau1_preimage[i];
|
||||
state_rsp, offset = absorb_one_byte(state_rsp, offset, c);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
state_rsp = cshake256_finalize(state_rsp, offset);
|
||||
|
||||
@@ -171,12 +260,18 @@ export fn pulsar_round1_commit(
|
||||
reg u64 outputs_ptr)
|
||||
-> reg u64
|
||||
{
|
||||
// Derived output pointers within outputs_ptr.
|
||||
// Output base address. We keep it in a stack slot and only
|
||||
// materialise the two derived output pointers (commit/y_state) into
|
||||
// registers in the final write phase (steps 9-10). The middle of
|
||||
// this function inlines several keccak permutations whose register
|
||||
// demand is high; pinning two GP registers to long-lived output
|
||||
// pointers across that region exhausts the allocator. Spilling the
|
||||
// base and re-deriving late keeps the same addresses with no extra
|
||||
// live registers during the hash-heavy section.
|
||||
stack u64 outputs_base;
|
||||
reg u64 commit_out_ptr;
|
||||
reg u64 y_state_out_ptr;
|
||||
commit_out_ptr = outputs_ptr;
|
||||
y_state_out_ptr = outputs_ptr;
|
||||
y_state_out_ptr += PULSARM_COMMIT_BYTES;
|
||||
outputs_base = outputs_ptr;
|
||||
|
||||
// Stack workspace
|
||||
stack u8[16] sid;
|
||||
@@ -188,15 +283,19 @@ export fn pulsar_round1_commit(
|
||||
stack u8[32] tau1_digest;
|
||||
stack u8[Li2_pack_w1len] w1_packed;
|
||||
stack u8[32] D_i;
|
||||
// tau_1 preimage (sid||attempt_be||node_id||pk_seed||mu), assembled
|
||||
// once and shared by derive_rho_prime and round1_tau1_digest.
|
||||
stack u8[PULSARM_TAU1_PREIMAGE_LEN] tau1_preimage;
|
||||
stack u32[Li2_l * Li2_polydeg] y_i;
|
||||
stack u32[Li2_l * Li2_polydeg] fft_y_i;
|
||||
stack u32[Li2_k * Li2_l * Li2_polydeg] fft_matA;
|
||||
// fft_y_i / fft_matA / w0_i moved into round1_w_from_y (their own
|
||||
// register frame); only w_i and w1_i remain here as they feed the
|
||||
// y_state output below.
|
||||
stack u32[Li2_k * Li2_polydeg] w_i;
|
||||
stack u32[Li2_k * Li2_polydeg] w0_i;
|
||||
stack u32[Li2_k * Li2_polydeg] w1_i;
|
||||
stack u16 nonce_local;
|
||||
reg u16 nonce_reg;
|
||||
reg u64 i;
|
||||
reg u64 w_base;
|
||||
// Separate scratch bytes for the public-input copy loops (sid,
|
||||
// node_id, pk_seed, mu) versus the secret randomness copy loop.
|
||||
// Sharing `c` across all five would make jasmin-ct join its level
|
||||
@@ -214,6 +313,11 @@ export fn pulsar_round1_commit(
|
||||
reg ptr u8[32] tau1_rsp;
|
||||
reg ptr u8[32] D_rsp;
|
||||
reg ptr u8[Li2_pack_w1len] w1_rsp;
|
||||
reg ptr u32[Li2_l * Li2_polydeg] yi_rsp;
|
||||
reg ptr u32[Li2_k * Li2_polydeg] wi_rsp;
|
||||
reg ptr u32[Li2_k * Li2_polydeg] w1i_rsp;
|
||||
reg ptr u8[PULSARM_TAU1_PREIMAGE_LEN] preimage_rsp;
|
||||
reg u32 attempt_be;
|
||||
|
||||
// ---- 1. Copy inputs into stack workspace ----
|
||||
// Public inputs blob layout: [sid:16][node_id:32][pk_seed:32][mu:64]
|
||||
@@ -238,17 +342,32 @@ export fn pulsar_round1_commit(
|
||||
#declassify(pk_seed);
|
||||
#declassify(mu);
|
||||
|
||||
// ---- 1b. Assemble the tau_1 preimage once ----
|
||||
// Layout: sid(16) || attempt(BE32) || node_id(32) || pk_seed(32) || mu(64).
|
||||
// This matches the byte stream the previous five-operand absorb
|
||||
// produced; both derive_rho_prime and round1_tau1_digest consume it.
|
||||
?{}, i = #set0_64();
|
||||
while (i < 16) { c = sid[i]; tau1_preimage[i] = c; i += 1; }
|
||||
// Big-endian attempt bytes. Compute each byte into a register first;
|
||||
// a store of a shifted-then-truncated value is not a single x86
|
||||
// instruction. Byte order is unchanged (matches absorb_u32_be).
|
||||
attempt_be = attempt; attempt_be >>= 24; c = (8u) attempt_be; tau1_preimage[16] = c;
|
||||
attempt_be = attempt; attempt_be >>= 16; c = (8u) attempt_be; tau1_preimage[17] = c;
|
||||
attempt_be = attempt; attempt_be >>= 8; c = (8u) attempt_be; tau1_preimage[18] = c;
|
||||
attempt_be = attempt; c = (8u) attempt_be; tau1_preimage[19] = c;
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = node_id[i]; tau1_preimage[20 + i] = c; i += 1; }
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = pk_seed[i]; tau1_preimage[52 + i] = c; i += 1; }
|
||||
?{}, i = #set0_64();
|
||||
while (i < 64) { c = mu[i]; tau1_preimage[84 + i] = c; i += 1; }
|
||||
#declassify(tau1_preimage);
|
||||
|
||||
// ---- 2. Derive rho_prime_i via KMAC(K, tau_1, "PULSAR-SIGN-PRNG-V1") ----
|
||||
randomness_rsp = randomness;
|
||||
sid_rsp = sid;
|
||||
node_id_rsp = node_id;
|
||||
pk_seed_rsp = pk_seed;
|
||||
mu_rsp = mu;
|
||||
preimage_rsp = tau1_preimage;
|
||||
rho_prime_rsp = rho_prime;
|
||||
rho_prime_rsp = derive_rho_prime(
|
||||
randomness_rsp, sid_rsp, attempt,
|
||||
node_id_rsp, pk_seed_rsp, mu_rsp,
|
||||
rho_prime_rsp);
|
||||
rho_prime_rsp = derive_rho_prime(randomness_rsp, preimage_rsp, rho_prime_rsp);
|
||||
rho_prime = rho_prime_rsp;
|
||||
|
||||
// ---- 3. Sample y_i = expandMask(rho_prime, nonce = attempt) ----
|
||||
@@ -257,31 +376,26 @@ export fn pulsar_round1_commit(
|
||||
nonce_reg = nonce_local;
|
||||
y_i = expandMask(rho_prime_rsp, nonce_reg);
|
||||
|
||||
// ---- 4. Expand public matrix A from pk_seed ----
|
||||
fft_matA = expandA(pk_seed);
|
||||
|
||||
// ---- 5. Compute w_i = A * y_i in NTT domain, then bring back ----
|
||||
fft_y_i = deep_copy_vecl(y_i);
|
||||
fft_y_i = fft_vec(fft_y_i);
|
||||
w_i = mult_mat_vec(fft_matA, fft_y_i);
|
||||
w_i = reduce32_veck(w_i);
|
||||
w_i = ifft_to_mont_vec(w_i);
|
||||
w_i = polyveck_caddq(w_i);
|
||||
|
||||
// ---- 6. Decompose w_i = w1_i * 2*gamma2 + w0_i ----
|
||||
w0_i, w1_i = decompose_vec(w_i);
|
||||
// ---- 4-6. Expand A, compute w_i = A*y_i, decompose, pack w1 ----
|
||||
// Delegated to the non-inline round1_w_from_y so that expandA's large
|
||||
// inlined working set occupies its own register frame (see that
|
||||
// function's header). Results (w_i, w1_i, w1_packed) are written back
|
||||
// into the local stack buffers by reference.
|
||||
pk_seed_rsp = pk_seed;
|
||||
yi_rsp = y_i;
|
||||
wi_rsp = w_i;
|
||||
w1i_rsp = w1_i;
|
||||
w1_rsp = w1_packed;
|
||||
w1_rsp = pack_w1(w1_i);
|
||||
wi_rsp, w1i_rsp, w1_rsp =
|
||||
round1_w_from_y(pk_seed_rsp, yi_rsp, wi_rsp, w1i_rsp, w1_rsp);
|
||||
w_i = wi_rsp;
|
||||
w1_i = w1i_rsp;
|
||||
w1_packed = w1_rsp;
|
||||
|
||||
// ---- 7. Compute tau1_digest ----
|
||||
sid_rsp = sid;
|
||||
node_id_rsp = node_id;
|
||||
pk_seed_rsp = pk_seed;
|
||||
mu_rsp = mu;
|
||||
preimage_rsp = tau1_preimage;
|
||||
tau1_rsp = tau1_digest;
|
||||
tau1_rsp = round1_tau1_digest(
|
||||
sid_rsp, attempt, node_id_rsp, pk_seed_rsp, mu_rsp, tau1_rsp);
|
||||
tau1_rsp = round1_tau1_digest(preimage_rsp, tau1_rsp);
|
||||
tau1_digest = tau1_rsp;
|
||||
|
||||
// ---- 8. Compute D_i = cSHAKE256(pack_w1(w1_i)) ----
|
||||
@@ -291,6 +405,10 @@ export fn pulsar_round1_commit(
|
||||
D_i = D_rsp;
|
||||
|
||||
// ---- 9. Write commit_out ----
|
||||
// Re-derive the output pointers now (see outputs_base comment above).
|
||||
commit_out_ptr = outputs_base;
|
||||
y_state_out_ptr = outputs_base;
|
||||
y_state_out_ptr += PULSARM_COMMIT_BYTES;
|
||||
// [0..32)
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) {
|
||||
@@ -327,34 +445,36 @@ export fn pulsar_round1_commit(
|
||||
}
|
||||
|
||||
// ---- 10. Write y_state_out ----
|
||||
// y_i (l polys, 4 bytes per coeff, little-endian)
|
||||
// Each coefficient is emitted as one little-endian u32 store. On x86
|
||||
// a u32 store produces exactly the four LE bytes the previous
|
||||
// explicit byte-shift sequence wrote, so the output is byte-identical;
|
||||
// it also avoids the unencodable shifted-byte-store-with-compound-
|
||||
// address pattern the byte form produced after offset folding. The
|
||||
// per-poly base offset is folded into a base register (w_base) so the
|
||||
// address stays a simple [base + index*4] mode.
|
||||
// y_i (l polys)
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_l * Li2_polydeg) {
|
||||
r32 = y_i[i];
|
||||
[:u8 y_state_out_ptr + 4*i + 0] = (8u)(r32 >> 0);
|
||||
[:u8 y_state_out_ptr + 4*i + 1] = (8u)(r32 >> 8);
|
||||
[:u8 y_state_out_ptr + 4*i + 2] = (8u)(r32 >> 16);
|
||||
[:u8 y_state_out_ptr + 4*i + 3] = (8u)(r32 >> 24);
|
||||
[:u32 y_state_out_ptr + 4*i] = r32;
|
||||
i += 1;
|
||||
}
|
||||
// w_i (k polys)
|
||||
w_base = y_state_out_ptr;
|
||||
w_base += 4*(Li2_l*Li2_polydeg);
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_k * Li2_polydeg) {
|
||||
r32 = w_i[i];
|
||||
[:u8 y_state_out_ptr + 4*(Li2_l*Li2_polydeg) + 4*i + 0] = (8u)(r32 >> 0);
|
||||
[:u8 y_state_out_ptr + 4*(Li2_l*Li2_polydeg) + 4*i + 1] = (8u)(r32 >> 8);
|
||||
[:u8 y_state_out_ptr + 4*(Li2_l*Li2_polydeg) + 4*i + 2] = (8u)(r32 >> 16);
|
||||
[:u8 y_state_out_ptr + 4*(Li2_l*Li2_polydeg) + 4*i + 3] = (8u)(r32 >> 24);
|
||||
[:u32 w_base + 4*i] = r32;
|
||||
i += 1;
|
||||
}
|
||||
// w1_i (k polys)
|
||||
w_base = y_state_out_ptr;
|
||||
w_base += 4*((Li2_l + Li2_k)*Li2_polydeg);
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_k * Li2_polydeg) {
|
||||
r32 = w1_i[i];
|
||||
[:u8 y_state_out_ptr + 4*((Li2_l + Li2_k)*Li2_polydeg) + 4*i + 0] = (8u)(r32 >> 0);
|
||||
[:u8 y_state_out_ptr + 4*((Li2_l + Li2_k)*Li2_polydeg) + 4*i + 1] = (8u)(r32 >> 8);
|
||||
[:u8 y_state_out_ptr + 4*((Li2_l + Li2_k)*Li2_polydeg) + 4*i + 2] = (8u)(r32 >> 16);
|
||||
[:u8 y_state_out_ptr + 4*((Li2_l + Li2_k)*Li2_polydeg) + 4*i + 3] = (8u)(r32 >> 24);
|
||||
[:u32 w_base + 4*i] = r32;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
@@ -424,14 +544,18 @@ export fn pulsar_round1_mac(
|
||||
state_rsp, tag_rsp = kmac256_finalize_32(state_rsp, offset, tag_rsp);
|
||||
tag = tag_rsp;
|
||||
|
||||
// Write tag into commit_buf at peer_index slot.
|
||||
// Write tag into commit_buf at peer_index slot. Fold the buffer base
|
||||
// and the slot offset into a single register so the per-byte store is
|
||||
// an encodable [base + index] mode (base + slot + index would be
|
||||
// three address registers, which x86 cannot encode).
|
||||
slot_offset = (64u) peer_index;
|
||||
slot_offset *= 32;
|
||||
slot_offset += PULSARM_R1_MAC_OFFSET;
|
||||
slot_offset += commit_buf_ptr;
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) {
|
||||
c = tag[i];
|
||||
[:u8 commit_buf_ptr + slot_offset + i] = c;
|
||||
[:u8 slot_offset + i] = c;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
|
||||
+389
-111
@@ -54,55 +54,53 @@ from Jade require "crypto_sign/dilithium/dilithium3/amd64/ref/common.jinc"
|
||||
from Jade require "crypto_sign/dilithium/dilithium3/amd64/ref/packing.jinc"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MAC verification: for each peer j != my_index, recompute the expected
|
||||
// KMAC256(K_{i,j}, D_j || tau_1_j, 32, "PULSAR-SIGN-R1-MAC-V1") and
|
||||
// constant-time compare against the received MAC.
|
||||
// Recompute one peer's expected MAC:
|
||||
// expected = KMAC256(K, commit || tau1, 32, "PULSAR-SIGN-R1-MAC-V1").
|
||||
//
|
||||
// Returns 0 on full success; j+1 (encoding the first mismatched peer
|
||||
// index) on failure, so the caller can construct a ComplaintMACFailure
|
||||
// abort evidence with `accused = j`.
|
||||
// -----------------------------------------------------------------------------
|
||||
inline
|
||||
fn verify_peer_macs(
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] mac_keys,
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] commits,
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] tau1_digests,
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] received_macs,
|
||||
reg u32 quorum_size,
|
||||
reg u32 my_index)
|
||||
-> reg u64
|
||||
// `mac_data` is the contiguous 4-table buffer (see verify_peer_macs);
|
||||
// `base` = peer_index * 32 is the per-peer byte offset within each table.
|
||||
// The key / commit / tau sub-slices for this peer are copied into local
|
||||
// 32-byte buffers, then KMAC256 is run over commit || tau1 keyed by the
|
||||
// session key, squeezing the 32-byte tag into `expected_buf`.
|
||||
//
|
||||
// Real (stack-return) call: its keccak `state` (with the runtime-offset
|
||||
// byte view state[:u8 offset] inside kmac256_finalize_32) and theta
|
||||
// working set live in their own frame, so the caller verify_peer_macs
|
||||
// only needs to preserve its small loop state across the call rather than
|
||||
// hosting theta alongside it. Taking mac_data + base (rather than three
|
||||
// pre-sliced pointers) keeps both sides' working sets small.
|
||||
// Semantics/CT unchanged.
|
||||
#[returnaddress="stack"]
|
||||
fn r2_one_expected_mac(
|
||||
reg ptr u8[4 * PULSARM_QUORUM_MAX * 32] mac_data,
|
||||
reg u64 base,
|
||||
reg ptr u8[32] expected_buf)
|
||||
-> reg ptr u8[32]
|
||||
{
|
||||
stack u8[32] key_buf;
|
||||
stack u8[32] commit_buf;
|
||||
stack u8[32] tau_buf;
|
||||
stack u8[32] received_buf;
|
||||
stack u8[32] expected_buf;
|
||||
stack u64[25] state;
|
||||
reg ptr u64[25] state_rsp;
|
||||
reg ptr u8[32] key_rsp, commit_rsp, tau_rsp, recv_rsp, exp_rsp;
|
||||
reg u64 offset;
|
||||
reg u32 j;
|
||||
reg u64 base;
|
||||
reg u64 i;
|
||||
reg ptr u8[32] key_rsp, commit_rsp, tau_rsp, exp_rsp;
|
||||
reg ptr u8[32] src_slice;
|
||||
reg u64 offset, i;
|
||||
reg u8 c;
|
||||
reg u8 eq;
|
||||
reg u64 status;
|
||||
|
||||
?{}, status = #set0_64();
|
||||
?{}, j = #set0_32();
|
||||
while (j < quorum_size) {
|
||||
if (j != my_index) {
|
||||
base = (64u) j;
|
||||
base *= 32;
|
||||
|
||||
// Copy this peer's key / commit / tau1 (sub-regions [0]/[1]/[2]).
|
||||
// Take a 32-byte reg-ptr slice of the peer's row in each sub-region,
|
||||
// then index by i alone — addressing the stack array with two runtime
|
||||
// index terms (sub-region const + base + i) is not an encodable x86
|
||||
// address, whereas a runtime-offset slice plus single-index load is.
|
||||
src_slice = mac_data[0 * PULSARM_QUORUM_MAX * 32 + base : 32];
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = mac_keys[base + i]; key_buf[i] = c; i += 1; }
|
||||
while (i < 32) { c = src_slice[i]; key_buf[i] = c; i += 1; }
|
||||
src_slice = mac_data[1 * PULSARM_QUORUM_MAX * 32 + base : 32];
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = commits[base + i]; commit_buf[i] = c; i += 1; }
|
||||
while (i < 32) { c = src_slice[i]; commit_buf[i] = c; i += 1; }
|
||||
src_slice = mac_data[2 * PULSARM_QUORUM_MAX * 32 + base : 32];
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = tau1_digests[base + i]; tau_buf[i] = c; i += 1; }
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = received_macs[base + i]; received_buf[i] = c; i += 1; }
|
||||
while (i < 32) { c = src_slice[i]; tau_buf[i] = c; i += 1; }
|
||||
|
||||
state_rsp = state;
|
||||
key_rsp = key_buf;
|
||||
@@ -117,6 +115,111 @@ fn verify_peer_macs(
|
||||
exp_rsp = expected_buf;
|
||||
state_rsp, exp_rsp = kmac256_finalize_32(state_rsp, offset, exp_rsp);
|
||||
expected_buf = exp_rsp;
|
||||
return expected_buf;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MAC verification: for each peer j != my_index, recompute the expected
|
||||
// KMAC256(K_{i,j}, D_j || tau_1_j, 32, "PULSAR-SIGN-R1-MAC-V1") and
|
||||
// constant-time compare against the received MAC.
|
||||
//
|
||||
// Returns 0 on full success; j+1 (encoding the first mismatched peer
|
||||
// index) on failure, so the caller can construct a ComplaintMACFailure
|
||||
// abort evidence with `accused = j`.
|
||||
//
|
||||
// Compiled as a real (stack-return-address) call. The whole MAC region —
|
||||
// this loop, the per-peer buffers and the inlined r2_one_expected_mac
|
||||
// keccak — then lives in this function's own frame rather than flattening
|
||||
// into pulsar_round2 alongside its eleven unpacked caller pointers and
|
||||
// other hashes. The four per-peer byte tables are passed as ONE
|
||||
// contiguous buffer (mac_data, sub-regions [0]=mac_keys, [1]=commits,
|
||||
// [2]=tau1_digests, [3]=received_macs, each PULSARM_QUORUM_MAX*32 bytes),
|
||||
// and the two public u32 scalars are packed into a single u64 (quorum_size
|
||||
// in the high half, my_index in the low half), so the function needs only
|
||||
// two register parameters. The per-iteration working set — the inlined
|
||||
// r2_one_expected_mac keccak's theta plus mac_data, quorum_size, my_index,
|
||||
// the loop counter and status — sits right at the register-file limit, so
|
||||
// every freed parameter register matters. The runtime-offset keccak state
|
||||
// view is confined to this straight-line frame, so the stack-allocation
|
||||
// oracle accepts it. Semantics/CT unchanged (quorum_size / my_index public).
|
||||
//
|
||||
// Real (stack-return) call: keeps this loop and its call to
|
||||
// r2_one_expected_mac in a frame of its own, off pulsar_round2's heavily
|
||||
// populated frame (which already hosts the unpacked caller pointers and
|
||||
// the other digest calls).
|
||||
#[returnaddress="stack"]
|
||||
fn verify_peer_macs(
|
||||
reg ptr u8[4 * PULSARM_QUORUM_MAX * 32] mac_data,
|
||||
reg u64 qm_packed)
|
||||
-> reg u64
|
||||
{
|
||||
stack u8[32] received_buf;
|
||||
stack u8[32] expected_buf;
|
||||
reg ptr u8[32] recv_rsp, exp_rsp, recv_slice;
|
||||
// quorum_size / my_index are loop-invariant and only read at the loop
|
||||
// boundary, never inside the inlined keccak. Hold them in stack slots
|
||||
// so they do not occupy registers across the theta-heavy MAC
|
||||
// recomputation (that is what pushed the frame one register over).
|
||||
stack u32 quorum_size_s;
|
||||
stack u32 my_index_s;
|
||||
stack u32 j_s;
|
||||
stack u64 status_s;
|
||||
reg u32 quorum_size;
|
||||
reg u32 my_index;
|
||||
reg u64 tmp64;
|
||||
reg u32 j;
|
||||
reg u64 base;
|
||||
reg u64 i;
|
||||
reg u8 c;
|
||||
reg u8 eq;
|
||||
reg u64 status;
|
||||
|
||||
// Unpack the two public scalars (high 32 = quorum_size, low = my_index).
|
||||
tmp64 = qm_packed;
|
||||
tmp64 >>= 32;
|
||||
quorum_size = (32u) tmp64;
|
||||
quorum_size_s = quorum_size;
|
||||
my_index = (32u) qm_packed;
|
||||
my_index_s = my_index;
|
||||
|
||||
// status AND the loop counter j live in stack slots across the
|
||||
// per-iteration r2_one_expected_mac call: every value kept in a
|
||||
// (callee-saved) register across that call competes with the callee's
|
||||
// 10-register keccak working set in Jasmin's global allocator, which
|
||||
// is what exhausts the file. After this, only mac_data (the call's own
|
||||
// argument) and the call's result pointer cross the boundary. base is
|
||||
// likewise recomputed from j rather than held.
|
||||
?{}, status = #set0_64();
|
||||
status_s = status;
|
||||
?{}, j = #set0_32();
|
||||
j_s = j;
|
||||
quorum_size = quorum_size_s;
|
||||
while (j < quorum_size) {
|
||||
// The per-peer MAC recomputation is done unconditionally for
|
||||
// every j (including j == my_index), and only the accusation
|
||||
// update is gated on j != my_index. (Self index excluded from
|
||||
// accusations.) j / my_index are public, so this introduces no
|
||||
// secret-dependent behaviour; the access pattern is uniform.
|
||||
// base = j*32; each table t starts at t*PULSARM_QUORUM_MAX*32.
|
||||
base = (64u) j;
|
||||
base *= 32;
|
||||
|
||||
// Recompute this peer's expected MAC in its own frame. j is in a
|
||||
// stack slot across the call (see note above).
|
||||
exp_rsp = expected_buf;
|
||||
exp_rsp = r2_one_expected_mac(mac_data, base, exp_rsp);
|
||||
expected_buf = exp_rsp;
|
||||
|
||||
// Received MAC is sub-region [3]. Reload j and recompute base
|
||||
// (neither held across the call). Slice the peer's row, then index
|
||||
// by i (two runtime index terms into the stack array would not be
|
||||
// an encodable address; a runtime-offset slice is).
|
||||
j = j_s;
|
||||
base = (64u) j;
|
||||
base *= 32;
|
||||
recv_slice = mac_data[3 * PULSARM_QUORUM_MAX * 32 + base : 32];
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = recv_slice[i]; received_buf[i] = c; i += 1; }
|
||||
|
||||
recv_rsp = received_buf;
|
||||
exp_rsp = expected_buf;
|
||||
@@ -128,6 +231,11 @@ fn verify_peer_macs(
|
||||
// is a protocol-level acceptance: the verifier learns
|
||||
// accept/reject, which is exactly what `eq` encodes.
|
||||
#declassify(eq);
|
||||
// Reload the loop-invariant scalars and status now (after the
|
||||
// keccak call), so they were not pinned to registers across it.
|
||||
my_index = my_index_s;
|
||||
status = status_s;
|
||||
if (j != my_index) {
|
||||
if (eq == 0) {
|
||||
if (status == 0) {
|
||||
status = (64u) j;
|
||||
@@ -135,15 +243,24 @@ fn verify_peer_macs(
|
||||
}
|
||||
}
|
||||
}
|
||||
status_s = status;
|
||||
j = j_s;
|
||||
j += 1;
|
||||
j_s = j;
|
||||
quorum_size = quorum_size_s;
|
||||
}
|
||||
status = status_s;
|
||||
return status;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Aggregate bar_D = cSHAKE256("PULSAR-SIGN-R2-V1" || D_1 || ... || D_n).
|
||||
// -----------------------------------------------------------------------------
|
||||
inline
|
||||
// Stack-return call (not inlined): keeps its keccak `state` working set
|
||||
// in its own frame instead of flattening into pulsar_round2 alongside the
|
||||
// other inlined hashes and the eleven unpacked caller pointers, which
|
||||
// together overflow the register file. Semantics/CT unchanged.
|
||||
#[returnaddress="stack"]
|
||||
fn r2_aggregate_commit_digest(
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] commits,
|
||||
reg u32 quorum_size,
|
||||
@@ -152,26 +269,34 @@ fn r2_aggregate_commit_digest(
|
||||
{
|
||||
stack u64[25] state;
|
||||
reg ptr u64[25] state_rsp;
|
||||
// out and the byte count are loop-invariant / used only at the end;
|
||||
// hold them in stack slots so they do not occupy registers during the
|
||||
// keccak permutations driven inside the absorb loop (theta's working
|
||||
// set is large; the function is otherwise the same shape as round1's
|
||||
// commit-digest helper).
|
||||
stack u64 nbytes_s;
|
||||
reg u64 offset;
|
||||
reg u64 i;
|
||||
reg u64 base;
|
||||
reg u32 j;
|
||||
reg u64 nbytes;
|
||||
reg u8 c;
|
||||
|
||||
// The j-loop (per peer) over 32-byte commits is flattened to one byte
|
||||
// loop over commits[0 .. quorum_size*32); the absorbed stream is
|
||||
// identical (commits are contiguous, peer j at offset j*32).
|
||||
nbytes = (64u) quorum_size;
|
||||
nbytes *= 32;
|
||||
nbytes_s = nbytes;
|
||||
|
||||
state_rsp = state;
|
||||
state_rsp, offset = cshake256_absorb_prelude_R2(state_rsp);
|
||||
|
||||
?{}, j = #set0_32();
|
||||
while (j < quorum_size) {
|
||||
base = (64u) j;
|
||||
base *= 32;
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) {
|
||||
c = commits[base + i];
|
||||
nbytes = nbytes_s;
|
||||
while (i < nbytes) {
|
||||
c = commits[i];
|
||||
state_rsp, offset = absorb_one_byte(state_rsp, offset, c);
|
||||
i += 1;
|
||||
}
|
||||
j += 1;
|
||||
nbytes = nbytes_s;
|
||||
}
|
||||
|
||||
state_rsp = cshake256_finalize(state_rsp, offset);
|
||||
@@ -185,7 +310,9 @@ fn r2_aggregate_commit_digest(
|
||||
// Compute c_tilde = cSHAKE256("PULSAR-SIGN-R2-V1" || mu || pack_w1(bar_w))
|
||||
// truncated to 32 bytes (= 2*lambda for L3 / ML-DSA-65).
|
||||
// -----------------------------------------------------------------------------
|
||||
inline
|
||||
// Stack-return call (not inlined), same register-frame rationale as
|
||||
// r2_aggregate_commit_digest. Semantics/CT unchanged.
|
||||
#[returnaddress="stack"]
|
||||
fn r2_challenge_seed(
|
||||
reg ptr u8[64] mu,
|
||||
reg ptr u8[Li2_pack_w1len] bar_w_packed,
|
||||
@@ -201,6 +328,12 @@ fn r2_challenge_seed(
|
||||
state_rsp = state;
|
||||
state_rsp, offset = cshake256_absorb_prelude_R2(state_rsp);
|
||||
|
||||
// Absorb mu (64) then pack_w1(bar_w) (768). The bar_w loop crosses the
|
||||
// 136-byte rate boundary several times, so absorb_one_byte runs keccak
|
||||
// mid-loop. Keeping the squeeze-output pointer in a register across
|
||||
// that theta-heavy stretch competes with the permutation's working
|
||||
// set; index bar_w_packed directly and reload from stack as needed so
|
||||
// only the absorb cursor is live across the permutations.
|
||||
?{}, i = #set0_64();
|
||||
while (i < 64) {
|
||||
c = mu[i];
|
||||
@@ -220,6 +353,47 @@ fn r2_challenge_seed(
|
||||
return c_tilde_out;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// tau_2 = cSHAKE256("PULSAR-SIGN-R2-V1" || bar_D || mu || pack_w1(bar_w)),
|
||||
// truncated to 32 bytes. Stack-return call (not inlined) so its keccak
|
||||
// `state` working set stays in its own frame rather than flattening into
|
||||
// pulsar_round2 with the other hashes and the eleven unpacked caller
|
||||
// pointers. Absorbed byte stream and values are identical to the
|
||||
// previous inline body; CT unchanged.
|
||||
// -----------------------------------------------------------------------------
|
||||
#[returnaddress="stack"]
|
||||
fn r2_tau2_digest(
|
||||
reg ptr u8[32] bar_D,
|
||||
reg ptr u8[64] mu,
|
||||
reg ptr u8[Li2_pack_w1len] bar_w_packed,
|
||||
reg ptr u8[32] tau_out)
|
||||
-> reg ptr u8[32]
|
||||
{
|
||||
stack u64[25] state;
|
||||
reg ptr u64[25] state_rsp;
|
||||
reg ptr u8[32] bar_D_rsp;
|
||||
reg u64 offset;
|
||||
reg u64 i;
|
||||
reg u8 c;
|
||||
|
||||
state_rsp = state;
|
||||
state_rsp, offset = cshake256_absorb_prelude_R2(state_rsp);
|
||||
bar_D_rsp = bar_D;
|
||||
state_rsp, offset = absorb_array32(state_rsp, offset, bar_D_rsp);
|
||||
?{}, i = #set0_64();
|
||||
while (i < 64) { c = mu[i]; state_rsp, offset = absorb_one_byte(state_rsp, offset, c); i += 1; }
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_pack_w1len) {
|
||||
c = bar_w_packed[i];
|
||||
state_rsp, offset = absorb_one_byte(state_rsp, offset, c);
|
||||
i += 1;
|
||||
}
|
||||
state_rsp = cshake256_finalize(state_rsp, offset);
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = state_rsp[:u8 i]; tau_out[i] = c; i += 1; }
|
||||
return tau_out;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Round-2 main entry point.
|
||||
//
|
||||
@@ -266,24 +440,56 @@ fn r2_challenge_seed(
|
||||
// branching / load addresses). Per-buffer content sensitivity is
|
||||
// encoded by #declassify pragmas at the points where the protocol
|
||||
// declassifies them (e.g., w1 high-bits, signature challenge).
|
||||
#[ct = "public * public * public * public * public * public * public * public * public * public * public * public * public -> public"]
|
||||
// ABI note: the x86-64 SysV calling convention Jasmin targets passes at
|
||||
// most six arguments in registers, and Jasmin export functions require
|
||||
// every argument to be register-resident. The Round-2 entry point needs
|
||||
// eleven caller buffers plus two scalars, so the eleven pointers are
|
||||
// marshalled by the caller into one contiguous array of u64 addresses
|
||||
// (`ptrs_ptr`), in the fixed order documented below, and unpacked here.
|
||||
// This mirrors round1.jazz's "pack the public inputs into one blob"
|
||||
// convention. Only the addresses move into the blob — each buffer's
|
||||
// contents and sensitivity are exactly as before, so the per-buffer CT
|
||||
// classification (encoded via #declassify at the protocol declassify
|
||||
// points) and all computed values are unchanged.
|
||||
//
|
||||
// ptrs_ptr layout (each entry is a u64 little-endian pointer):
|
||||
// [ 0] y_state_ptr [ 1] share_ptr [ 2] blind_ptr
|
||||
// [ 3] peer_commits_ptr [ 4] peer_bar_w_ptr [ 5] peer_macs_ptr
|
||||
// [ 6] peer_tau_ptr [ 7] mac_keys_ptr [ 8] eval_points_ptr
|
||||
// [ 9] mu_ptr [10] response_out_ptr
|
||||
//
|
||||
// CT: ptrs_ptr and quorum_size / my_index are public.
|
||||
#[ct = "public * public * public -> public"]
|
||||
export
|
||||
fn pulsar_round2(
|
||||
reg u64 y_state_ptr,
|
||||
reg u64 share_ptr,
|
||||
reg u64 blind_ptr,
|
||||
reg u64 peer_commits_ptr,
|
||||
reg u64 peer_bar_w_ptr,
|
||||
reg u64 peer_macs_ptr,
|
||||
reg u64 peer_tau_ptr,
|
||||
reg u64 mac_keys_ptr,
|
||||
reg u64 eval_points_ptr,
|
||||
reg u64 mu_ptr,
|
||||
reg u64 ptrs_ptr,
|
||||
reg u32 quorum_size,
|
||||
reg u32 my_index,
|
||||
reg u64 response_out_ptr)
|
||||
reg u32 my_index)
|
||||
-> reg u64
|
||||
{
|
||||
reg u64 y_state_ptr;
|
||||
reg u64 share_ptr;
|
||||
reg u64 blind_ptr;
|
||||
reg u64 peer_commits_ptr;
|
||||
reg u64 peer_bar_w_ptr;
|
||||
reg u64 peer_macs_ptr;
|
||||
reg u64 peer_tau_ptr;
|
||||
reg u64 mac_keys_ptr;
|
||||
reg u64 eval_points_ptr;
|
||||
reg u64 mu_ptr;
|
||||
reg u64 response_out_ptr;
|
||||
// Spilled pointer-table base, so the two late-used pointers can be
|
||||
// reloaded lazily instead of staying live across the middle.
|
||||
stack u64 ptrs_base;
|
||||
reg u64 bw_base;
|
||||
// status spilled across the step 3-11 body (see its use site).
|
||||
stack u64 status_round2_s;
|
||||
// quorum_size / my_index spilled: they are read in several places
|
||||
// across the digest-call-heavy body, and a register pinned across any
|
||||
// keccak call there competes with the (globally allocated) keccak
|
||||
// permutation's working set. Reloaded from the stack before each use.
|
||||
stack u32 qs_s;
|
||||
stack u32 mi_s;
|
||||
// Workspace.
|
||||
stack u8[64] mu;
|
||||
stack u8[32] bar_D;
|
||||
@@ -296,30 +502,35 @@ fn pulsar_round2(
|
||||
stack u32[Li2_l * Li2_polydeg] tmp_l;
|
||||
stack u32[Li2_l * Li2_polydeg] cs_l;
|
||||
stack u32[Li2_l * Li2_polydeg] cu_l;
|
||||
stack u32[Li2_l * Li2_polydeg] scaled_l;
|
||||
stack u32[Li2_l * Li2_polydeg] z_i;
|
||||
stack u32[Li2_l * Li2_polydeg] r_i;
|
||||
stack u32[Li2_polydeg] c_poly;
|
||||
stack u32[Li2_polydeg] c_ntt;
|
||||
stack u32[Li2_k * Li2_polydeg] bar_w;
|
||||
stack u32[Li2_k * Li2_polydeg] my_w1;
|
||||
stack u8[PULSARM_QUORUM_MAX * 32] commit_buf;
|
||||
stack u8[PULSARM_QUORUM_MAX * 32] mac_key_buf;
|
||||
stack u8[PULSARM_QUORUM_MAX * 32] mac_buf;
|
||||
stack u8[PULSARM_QUORUM_MAX * 32] tau_buf;
|
||||
// The four per-peer byte tables are held in one contiguous buffer so
|
||||
// verify_peer_macs can take a single pointer (keeping its register-
|
||||
// parameter count low; see its header). Sub-region layout, each
|
||||
// PULSARM_QUORUM_MAX*32 bytes:
|
||||
// [0] mac_keys [1] commits [2] tau1_digests [3] received_macs
|
||||
stack u8[4 * PULSARM_QUORUM_MAX * 32] peer_mac_data;
|
||||
stack u32[PULSARM_QUORUM_MAX] eval_pts;
|
||||
stack u64[25] state;
|
||||
|
||||
reg ptr u8[64] mu_rsp;
|
||||
reg ptr u8[32] bar_D_rsp, c_tilde_rsp;
|
||||
reg ptr u8[32] bar_D_rsp, c_tilde_rsp, tau2_rsp;
|
||||
reg ptr u8[Li2_pack_w1len] packed_rsp;
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] commits_rsp, mkey_rsp, mac_rsp, tau_rsp;
|
||||
reg ptr u8[PULSARM_QUORUM_MAX * 32] commits_rsp;
|
||||
reg ptr u8[4 * PULSARM_QUORUM_MAX * 32] mac_data_rsp;
|
||||
reg ptr u32[Li2_l * Li2_polydeg] vec_l_rsp;
|
||||
reg ptr u32[Li2_l * Li2_polydeg] vec_l_out;
|
||||
reg ptr u32[Li2_k * Li2_polydeg] vec_k_rsp;
|
||||
reg ptr u32[Li2_polydeg] pc_rsp, pn_rsp;
|
||||
reg ptr u32[PULSARM_QUORUM_MAX] eval_rsp;
|
||||
reg ptr u64[25] state_rsp;
|
||||
|
||||
reg u64 i, offset, base;
|
||||
reg u64 qm_packed, tmp_qm;
|
||||
reg u32 j;
|
||||
// Separate scratch bytes for the public-input load loops vs the
|
||||
// secret-content load loop (mac_keys / share / blind). Sharing
|
||||
@@ -331,6 +542,27 @@ fn pulsar_round2(
|
||||
reg u32 lambda_mont;
|
||||
reg u64 status;
|
||||
|
||||
// ---- 0. Unpack the caller-supplied pointer table (see ABI note) ----
|
||||
// Each entry is a u64 address. Pointers used only by the early input-
|
||||
// loading phase are unpacked now; peer_bar_w_ptr (step 4) and
|
||||
// response_out_ptr (step 11) are re-loaded lazily from the spilled
|
||||
// table right before their use, so they do not stay live across the
|
||||
// hash-heavy middle and inflate register pressure there.
|
||||
ptrs_base = ptrs_ptr;
|
||||
y_state_ptr = [:u64 ptrs_ptr + 0];
|
||||
share_ptr = [:u64 ptrs_ptr + 8];
|
||||
blind_ptr = [:u64 ptrs_ptr + 16];
|
||||
peer_commits_ptr = [:u64 ptrs_ptr + 24];
|
||||
peer_macs_ptr = [:u64 ptrs_ptr + 40];
|
||||
peer_tau_ptr = [:u64 ptrs_ptr + 48];
|
||||
mac_keys_ptr = [:u64 ptrs_ptr + 56];
|
||||
eval_points_ptr = [:u64 ptrs_ptr + 64];
|
||||
mu_ptr = [:u64 ptrs_ptr + 72];
|
||||
|
||||
// Spill the two public scalars immediately (reloaded before each use).
|
||||
qs_s = quorum_size;
|
||||
mi_s = my_index;
|
||||
|
||||
// ---- 1. Load inputs ----
|
||||
?{}, i = #set0_64();
|
||||
while (i < 64) { c = [:u8 mu_ptr + i]; mu[i] = c; i += 1; }
|
||||
@@ -343,10 +575,14 @@ fn pulsar_round2(
|
||||
i += 1;
|
||||
}
|
||||
// my_w1 = last Li2_k polynomials of y_state (offset = (Li2_l + Li2_k) polynomials).
|
||||
// Fold the base pointer and the constant poly offset into one register
|
||||
// so each load is [base + index*4]; base-pointer + offset-register +
|
||||
// index*scale is two base registers, which x86 cannot encode.
|
||||
base = (64u) (Li2_l + Li2_k) * Li2_polydeg * 4;
|
||||
base += y_state_ptr;
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_k * Li2_polydeg) {
|
||||
r32 = [:u32 y_state_ptr + base + 4 * i];
|
||||
r32 = [:u32 base + 4 * i];
|
||||
my_w1[i] = r32;
|
||||
i += 1;
|
||||
}
|
||||
@@ -366,6 +602,7 @@ fn pulsar_round2(
|
||||
}
|
||||
|
||||
// eval_points (public).
|
||||
quorum_size = qs_s;
|
||||
?{}, j = #set0_32();
|
||||
while (j < quorum_size) {
|
||||
base = (64u) j;
|
||||
@@ -378,23 +615,34 @@ fn pulsar_round2(
|
||||
// mac_keys are secret session keys K_{i,j}). Use a separate
|
||||
// scratch byte for the secret mac_keys load so jasmin-ct does
|
||||
// not back-propagate secrecy onto the public commit / mac / tau
|
||||
// buffers via a shared `c` register.
|
||||
// buffers via a shared `c` register. The four tables are stored in
|
||||
// the contiguous peer_mac_data sub-regions described at its decl.
|
||||
?{}, i = #set0_64();
|
||||
while (i < PULSARM_QUORUM_MAX * 32) {
|
||||
c = [:u8 peer_commits_ptr + i]; commit_buf[i] = c;
|
||||
c_sec = [:u8 mac_keys_ptr + i]; mac_key_buf[i] = c_sec;
|
||||
c = [:u8 peer_macs_ptr + i]; mac_buf[i] = c;
|
||||
c = [:u8 peer_tau_ptr + i]; tau_buf[i] = c;
|
||||
c_sec = [:u8 mac_keys_ptr + i]; peer_mac_data[0 * PULSARM_QUORUM_MAX * 32 + i] = c_sec;
|
||||
c = [:u8 peer_commits_ptr + i]; peer_mac_data[1 * PULSARM_QUORUM_MAX * 32 + i] = c;
|
||||
c = [:u8 peer_tau_ptr + i]; peer_mac_data[2 * PULSARM_QUORUM_MAX * 32 + i] = c;
|
||||
c = [:u8 peer_macs_ptr + i]; peer_mac_data[3 * PULSARM_QUORUM_MAX * 32 + i] = c;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// ---- 2. Verify peer MACs ----
|
||||
mkey_rsp = mac_key_buf;
|
||||
commits_rsp = commit_buf;
|
||||
tau_rsp = tau_buf;
|
||||
mac_rsp = mac_buf;
|
||||
status = verify_peer_macs(mkey_rsp, commits_rsp, tau_rsp, mac_rsp,
|
||||
quorum_size, my_index);
|
||||
// Pack the two public scalars into one u64 (quorum_size high, my_index
|
||||
// low) so verify_peer_macs stays within two register parameters.
|
||||
mac_data_rsp = peer_mac_data;
|
||||
quorum_size = qs_s;
|
||||
qm_packed = (64u) quorum_size;
|
||||
qm_packed <<= 32;
|
||||
my_index = mi_s;
|
||||
tmp_qm = (64u) my_index;
|
||||
qm_packed |= tmp_qm;
|
||||
status = verify_peer_macs(mac_data_rsp, qm_packed);
|
||||
// Spill status to a stack slot: it must stay live across the entire
|
||||
// step 3-11 body (it gates the body and is the return value), and a
|
||||
// register pinned across every digest call in that body competes with
|
||||
// each callee's keccak working set in the global allocator. It is not
|
||||
// modified inside the body, so we just reload it at the return.
|
||||
status_round2_s = status;
|
||||
// Jasmin doesn't accept early `return` inside an `if`; fall through
|
||||
// and gate the rest of the body on `status == 0`. The MAC-failure
|
||||
// path leaves the output buffer in an indeterminate state, which
|
||||
@@ -402,8 +650,10 @@ fn pulsar_round2(
|
||||
if (status == 0) {
|
||||
|
||||
// ---- 3. Aggregate bar_D ----
|
||||
commits_rsp = commit_buf;
|
||||
// commits are sub-region [1] of peer_mac_data.
|
||||
commits_rsp = peer_mac_data[1 * PULSARM_QUORUM_MAX * 32 : PULSARM_QUORUM_MAX * 32];
|
||||
bar_D_rsp = bar_D;
|
||||
quorum_size = qs_s;
|
||||
bar_D_rsp = r2_aggregate_commit_digest(commits_rsp, quorum_size, bar_D_rsp);
|
||||
|
||||
// ---- 4. Aggregate bar_w = sum_j bar_w_j (mod q) ----
|
||||
@@ -414,14 +664,25 @@ fn pulsar_round2(
|
||||
bar_w[i] = my_w1[i];
|
||||
i += 1;
|
||||
}
|
||||
// Reload peer_bar_w_ptr lazily here (entry [4] of the pointer table),
|
||||
// so it is not live across the earlier MAC-verify / digest phase.
|
||||
peer_bar_w_ptr = ptrs_base;
|
||||
peer_bar_w_ptr = [:u64 peer_bar_w_ptr + 32];
|
||||
quorum_size = qs_s;
|
||||
my_index = mi_s;
|
||||
?{}, j = #set0_32();
|
||||
while (j < quorum_size) {
|
||||
if (j != my_index) {
|
||||
base = (64u) j;
|
||||
base *= Li2_k * Li2_polydeg * 4;
|
||||
// Fold the buffer base and the per-peer stride into one
|
||||
// register so the inner load is an encodable [base + index*4]
|
||||
// mode (base + per-peer-offset + index*scale would be two base
|
||||
// registers plus index, which x86 cannot encode).
|
||||
bw_base = (64u) j;
|
||||
bw_base *= Li2_k * Li2_polydeg * 4;
|
||||
bw_base += peer_bar_w_ptr;
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_k * Li2_polydeg) {
|
||||
r32 = [:u32 peer_bar_w_ptr + base + 4 * i];
|
||||
r32 = [:u32 bw_base + 4 * i];
|
||||
sum32 = bar_w[i];
|
||||
sum32 += r32;
|
||||
// Reduce mod 2^32; canonical reduction happens at pack_w1 stage
|
||||
@@ -434,10 +695,14 @@ fn pulsar_round2(
|
||||
}
|
||||
|
||||
// Pack bar_w for c_tilde input and for transcript binding.
|
||||
// Assign the pack_w1 result (a fresh stack array) directly into the
|
||||
// stack buffer rather than bouncing through a reg-ptr; routing an
|
||||
// inline function's returned stack array through a reg ptr and back
|
||||
// into a stack array trips jasminc's variable-allocation oracle
|
||||
// ("variable already set"). Direct stack-to-stack assignment is the
|
||||
// pattern libjade itself uses and computes the identical bytes.
|
||||
vec_k_rsp = bar_w;
|
||||
packed_rsp = bar_w_packed;
|
||||
packed_rsp = pack_w1(vec_k_rsp);
|
||||
bar_w_packed = packed_rsp;
|
||||
bar_w_packed = pack_w1(vec_k_rsp);
|
||||
|
||||
// ---- 5. c_tilde = SHAKE(mu || pack_w1(bar_w)) ----
|
||||
mu_rsp = mu;
|
||||
@@ -466,7 +731,10 @@ fn pulsar_round2(
|
||||
c_ntt = pn_rsp;
|
||||
|
||||
// ---- 7. lambda_i^T in Montgomery form ----
|
||||
// Reload the scalars (after sampleInBall's keccak).
|
||||
eval_rsp = eval_pts;
|
||||
my_index = mi_s;
|
||||
quorum_size = qs_s;
|
||||
lambda_mont = lagrange_coefficient_mont(eval_rsp, my_index, quorum_size);
|
||||
|
||||
// ---- 8. z_i = y_i + (c * lambda_i * s_i) ----
|
||||
@@ -484,10 +752,17 @@ fn pulsar_round2(
|
||||
vec_l_rsp = cs_l;
|
||||
vec_l_rsp = ifft_to_mont_vecl(vec_l_rsp);
|
||||
cs_l = vec_l_rsp;
|
||||
// d) scale by lambda_i (Montgomery form)
|
||||
// d) scale by lambda_i (Montgomery form). Read from cs_l, write to a
|
||||
// dedicated scratch buffer scaled_l, then copy back. Distinct input
|
||||
// and output regions are required because handing one reg ptr to both
|
||||
// the `v` and `out` parameters of polyvecl_scalar_mont tripped
|
||||
// jasminc's variable-allocation oracle, while aliasing the output onto
|
||||
// another already-aliased scratch (tmp_l) made its region "partial".
|
||||
// out[k]=REDC(lambda*v[k]) is per-coefficient; values/CT unchanged.
|
||||
vec_l_rsp = cs_l;
|
||||
vec_l_rsp = polyvecl_scalar_mont(lambda_mont, vec_l_rsp, vec_l_rsp);
|
||||
cs_l = vec_l_rsp;
|
||||
vec_l_out = scaled_l;
|
||||
vec_l_out = polyvecl_scalar_mont(lambda_mont, vec_l_rsp, vec_l_out);
|
||||
cs_l = vec_l_out;
|
||||
// e) z_i = y_i + cs_l
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_l * Li2_polydeg) {
|
||||
@@ -513,9 +788,11 @@ fn pulsar_round2(
|
||||
vec_l_rsp = cu_l;
|
||||
vec_l_rsp = ifft_to_mont_vecl(vec_l_rsp);
|
||||
cu_l = vec_l_rsp;
|
||||
// Read cu_l, write scaled_l, copy back (see step 8d note).
|
||||
vec_l_rsp = cu_l;
|
||||
vec_l_rsp = polyvecl_scalar_mont(lambda_mont, vec_l_rsp, vec_l_rsp);
|
||||
cu_l = vec_l_rsp;
|
||||
vec_l_out = scaled_l;
|
||||
vec_l_out = polyvecl_scalar_mont(lambda_mont, vec_l_rsp, vec_l_out);
|
||||
cu_l = vec_l_out;
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_l * Li2_polydeg) { r_i[i] = cu_l[i]; i += 1; }
|
||||
vec_l_rsp = r_i;
|
||||
@@ -523,23 +800,19 @@ fn pulsar_round2(
|
||||
r_i = vec_l_rsp;
|
||||
|
||||
// ---- 10. tau_2 = cSHAKE("PULSAR-SIGN-R2-V1" || bar_D || mu || pack_w1(bar_w)) ----
|
||||
state_rsp = state;
|
||||
state_rsp, offset = cshake256_absorb_prelude_R2(state_rsp);
|
||||
// Delegated to r2_tau2_digest (own register frame; see its header).
|
||||
bar_D_rsp = bar_D;
|
||||
state_rsp, offset = absorb_array32(state_rsp, offset, bar_D_rsp);
|
||||
?{}, i = #set0_64();
|
||||
while (i < 64) { c = mu[i]; state_rsp, offset = absorb_one_byte(state_rsp, offset, c); i += 1; }
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_pack_w1len) {
|
||||
c = bar_w_packed[i];
|
||||
state_rsp, offset = absorb_one_byte(state_rsp, offset, c);
|
||||
i += 1;
|
||||
}
|
||||
state_rsp = cshake256_finalize(state_rsp, offset);
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) { c = state_rsp[:u8 i]; tau2_digest[i] = c; i += 1; }
|
||||
mu_rsp = mu;
|
||||
packed_rsp = bar_w_packed;
|
||||
tau2_rsp = tau2_digest;
|
||||
tau2_rsp = r2_tau2_digest(bar_D_rsp, mu_rsp, packed_rsp, tau2_rsp);
|
||||
tau2_digest = tau2_rsp;
|
||||
|
||||
// ---- 11. Write response buffer ----
|
||||
// Reload response_out_ptr lazily here (entry [10] of the pointer
|
||||
// table); it is only needed by this final write phase.
|
||||
response_out_ptr = ptrs_base;
|
||||
response_out_ptr = [:u64 response_out_ptr + 80];
|
||||
// [0 .. 32) tau_2
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) {
|
||||
@@ -587,10 +860,9 @@ fn pulsar_round2(
|
||||
}
|
||||
|
||||
// [7776 .. 8544) pack_w1(my_w1)
|
||||
// Direct stack-to-stack assignment (see the bar_w pack_w1 note above).
|
||||
vec_k_rsp = my_w1;
|
||||
packed_rsp = bar_w_packed; // reuse the same stack buffer
|
||||
packed_rsp = pack_w1(vec_k_rsp);
|
||||
bar_w_packed = packed_rsp;
|
||||
bar_w_packed = pack_w1(vec_k_rsp);
|
||||
?{}, i = #set0_64();
|
||||
while (i < Li2_pack_w1len) {
|
||||
c = bar_w_packed[i];
|
||||
@@ -614,6 +886,8 @@ fn pulsar_round2(
|
||||
|
||||
// status already holds 0 (MAC verify passed); no need to reset
|
||||
} // end "if (status == 0)" body
|
||||
// Reload status from its stack slot (it was not modified in the body).
|
||||
status = status_round2_s;
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -668,13 +942,17 @@ fn pulsar_round2_mac(
|
||||
state_rsp, tag_rsp = kmac256_finalize_32(state_rsp, offset, tag_rsp);
|
||||
tag = tag_rsp;
|
||||
|
||||
// Fold the buffer base and the slot offset into one register so the
|
||||
// per-byte store is an encodable [base + index] mode (base + slot +
|
||||
// index would be three address registers, which x86 cannot encode).
|
||||
slot_offset = (64u) peer_index;
|
||||
slot_offset *= 32;
|
||||
slot_offset += PULSARM_R2_MAC_OFFSET;
|
||||
slot_offset += response_buf_ptr;
|
||||
?{}, i = #set0_64();
|
||||
while (i < 32) {
|
||||
c = tag[i];
|
||||
[:u8 response_buf_ptr + slot_offset + i] = c;
|
||||
[:u8 slot_offset + i] = c;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user