mirror of
https://github.com/luxfi/pulsar.git
synced 2026-09-24 00:25:52 +00:00
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]>
143 lines
5.0 KiB
Plaintext
143 lines
5.0 KiB
Plaintext
// Per-party, per-attempt rho_prime derivation for Pulsar Round-1.
|
|
//
|
|
// Per spec/pulsar-m.tex ยง4.4 (Round 1, line 757):
|
|
//
|
|
// seed_i = KMAC256(K_i^prng, tau_1, 256, "PULSAR-SIGN-PRNG-V1")
|
|
// rho_prime_i = SHAKE256(seed_i, 64)
|
|
//
|
|
// where K_i^prng is this party's long-lived PRNG key (32 bytes) and
|
|
// tau_1 = (sid, kappa, T, i, pk, mu). The 256-bit seed_i is then
|
|
// expanded to the 64-byte FIPS 204 rho_prime via a plain SHAKE256
|
|
// absorption, so libjade's `expandMask` can consume it unchanged.
|
|
//
|
|
// We follow the same construction as ref/go/pkg/pulsar/transcript.go
|
|
// so the Jasmin and Go signers agree byte-for-byte on rho_prime.
|
|
|
|
require "pulsar_params.jinc"
|
|
require "transcript.jinc"
|
|
require "mac.jinc"
|
|
from Jade require "crypto_sign/dilithium/common/amd64/fips202.jinc"
|
|
|
|
// Absorb the cSHAKE256 prelude for KMAC256 with N="KMAC",
|
|
// S="PULSAR-SIGN-PRNG-V1" (18 bytes -> 144 bits = 0x90), then the
|
|
// key K. After this routine, state is ready to absorb the message X.
|
|
inline
|
|
fn kmac256_init_PRNG(reg ptr u64[25] state, reg ptr u8[32] K)
|
|
-> reg ptr u64[25]
|
|
{
|
|
reg u64 i;
|
|
reg u8 c;
|
|
|
|
state = st0(state);
|
|
state[:u8 0] = 0x01;
|
|
state[:u8 1] = 0x88;
|
|
state[:u8 2] = 0x01;
|
|
state[:u8 3] = 0x20;
|
|
state[:u8 4] = 0x4b; state[:u8 5] = 0x4d; state[:u8 6] = 0x41;
|
|
state[:u8 7] = 0x43;
|
|
state[:u8 8] = 0x01;
|
|
state[:u8 9] = 0x90;
|
|
state[:u8 10] = 0x50; state[:u8 11] = 0x55; state[:u8 12] = 0x4c;
|
|
state[:u8 13] = 0x53; state[:u8 14] = 0x41; state[:u8 15] = 0x52;
|
|
state[:u8 16] = 0x2d; state[:u8 17] = 0x53; state[:u8 18] = 0x49;
|
|
state[:u8 19] = 0x47; state[:u8 20] = 0x4e; state[:u8 21] = 0x2d;
|
|
state[:u8 22] = 0x50; state[:u8 23] = 0x52; state[:u8 24] = 0x4e;
|
|
state[:u8 25] = 0x47; state[:u8 26] = 0x2d; state[:u8 27] = 0x56;
|
|
state[:u8 28] = 0x31;
|
|
state = __keccakf1600_ref(state);
|
|
|
|
// Key prefix: bytepad(encode_string(K), 136), |K|=32.
|
|
state[:u8 0] ^= 0x01;
|
|
state[:u8 1] ^= 0x88;
|
|
state[:u8 2] ^= 0x02;
|
|
state[:u8 3] ^= 0x01;
|
|
state[:u8 4] ^= 0x00;
|
|
?{}, i = #set0_64();
|
|
while (i < 32) {
|
|
c = K[i];
|
|
state[:u8 5 + i] ^= c;
|
|
i += 1;
|
|
}
|
|
state = __keccakf1600_ref(state);
|
|
|
|
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)
|
|
// 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.
|
|
//
|
|
// 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[PULSARM_TAU1_PREIMAGE_LEN] tau1_preimage,
|
|
reg ptr u8[64] rho_prime_out)
|
|
-> reg ptr u8[64]
|
|
{
|
|
stack u64[25] state;
|
|
reg ptr u64[25] state_rsp;
|
|
stack u8[32] seed_i;
|
|
reg ptr u8[32] seed_i_rsp;
|
|
reg ptr u8[32] tag_rsp;
|
|
reg u64 i, offset;
|
|
reg u8 c;
|
|
|
|
state_rsp = state;
|
|
state_rsp = kmac256_init_PRNG(state_rsp, K);
|
|
|
|
// Absorb tau_1 = sid || attempt(BE32) || my_node_id || pk_seed || mu,
|
|
// already laid out contiguously in tau1_preimage.
|
|
?{}, offset = #set0_64();
|
|
?{}, 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;
|
|
state_rsp, seed_i_rsp = kmac256_finalize_32(state_rsp, offset, seed_i_rsp);
|
|
seed_i = seed_i_rsp;
|
|
|
|
// ---- Expand seed_i to 64 bytes via SHAKE256 ----
|
|
state_rsp = st0(state_rsp);
|
|
?{}, i = #set0_64();
|
|
while (i < 32) {
|
|
c = seed_i[i];
|
|
state_rsp[:u8 i] ^= c;
|
|
i += 1;
|
|
}
|
|
// SHAKE256 finaliser at offset 32, padding bit at rate-1.
|
|
state_rsp[:u8 32] ^= 0x1f;
|
|
state_rsp[:u8 SHAKE256_RATE - 1] ^= 0x80;
|
|
state_rsp = __keccakf1600_ref(state_rsp);
|
|
|
|
?{}, i = #set0_64();
|
|
while (i < 64) {
|
|
c = state_rsp[:u8 i];
|
|
rho_prime_out[i] = c;
|
|
i += 1;
|
|
}
|
|
return rho_prime_out;
|
|
}
|