Files
zeekay 75a04faa36 papers: best-effort accuracy fixes from swarm (dates, arithmetic, internal consistency, LP numbering)
Resolves high-severity review findings: anachronistic dates aligned to citation
floors; arithmetic corrected to match formulas; internal contradictions
collapsed to the body-supported value; LP self-references aligned to filenames;
liquidity-surface repo paths scrubbed; AI-slop trimmed. PDFs rebuilt separately.
2026-06-27 02:16:34 +00:00

390 lines
17 KiB
TeX

\section{Construction}
\label{sec:construction}
This section specifies the per-validator standalone construction
byte-for-byte. The pseudocode is keyed to the Go canonical at
\texttt{ref/go/pkg/magnetar/standalone.go} and
\texttt{ref/go/pkg/magnetar/wire.go} at tag \texttt{v1.0.0}. The
THBS-SE permissionless threshold companion is specified in
\S\ref{sec:thbsse}.
\subsection{Hash-domain separation}
\label{sec:construction:domains}
Every cryptographic hash invocation in Magnetar is cSHAKE256
\cite{sp800185} with function-name string \texttt{"Magnetar"} and a
per-purpose customisation string. The per-validator standalone path
uses only the wire-codec canonical magic constants (MAGS / MAGG)
plus the standard FIPS 205 dispatch; THBS-SE adds four
per-purpose tags listed in \S\ref{sec:thbsse:slotbind}.
\begin{table}[H]
\centering
\caption{Magnetar v1.0 cSHAKE256 domain separation. Every
secret-bearing hash invocation is bound to exactly one tag;
cross-tag replay reduces to cSHAKE256 collision resistance.
Tags marked \emph{(thresh)} appear only in the threshold-form
appendices.}
\label{tab:magnetar-tags}
\begin{tabular}{lp{8.5cm}}
\toprule
Tag & Purpose \\
\midrule
\texttt{MAGS} (magic 0x4D414753) & MAGS wire envelope for FIPS 205 signature payload (standalone primary) \\
\texttt{MAGG} (magic 0x4D414747) & MAGG wire envelope for FIPS 205 public-key payload (standalone primary) \\
\texttt{MAGNETAR-DKG-COMMIT-V1} & committee-root digest (sorted committee binding) \emph{(thresh)} \\
\texttt{MAGNETAR-DKG-TRANSCRIPT-V1} & DKG transcript digest (Round-2 equivocation check) \emph{(thresh)} \\
\texttt{MAGNETAR-SIGN-R1-V1} & threshold-sign Round-1 commit $D_i$ \emph{(thresh)} \\
\texttt{MAGNETAR-SIGN-MASK-V1} & per-attempt mask $r_i$ derivation \emph{(thresh)} \\
\texttt{MAGNETAR-SEED-SHARE-V1} & Shamir coefficient stream + final seed mix \emph{(thresh)} \\
\bottomrule
\end{tabular}
\end{table}
The protocol-wide domain separation context is the byte string
\texttt{"lux-magnetar-v0.5"} (\texttt{tagDomainSep}). It is bound
into the wire-codec magic constants and into every threshold-form
transcript binding so that a separate Magnetar version (or a
different Lux PQ primitive that happened to reuse the same hash
function) cannot collide.
\subsection{Per-validator keypair generation}
\label{sec:construction:keygen}
Each validator $i$ runs \texttt{PerValidatorKeypair} once at
validator-set onboarding, persists $(sk_i, pk_i)$ to local
HSM-backed storage, and registers $pk_i$ in the consensus layer's
validator-set commitment.
\paragraph{Algorithm (\texttt{standalone.go:197-199},
\texttt{keygen.go}).}
\begin{enumerate}[leftmargin=2em,nosep]
\item Pull \texttt{params} for the target \texttt{Mode}
(SHAKE-192s, SHAKE-192f, or SHAKE-256s).
\item Source $4n$ bytes of entropy from \texttt{rng} (where $n$ is
the FIPS 205 ``$n$'' parameter: 24 for 192, 32 for 256).
If \texttt{rng=nil}, source from \texttt{crypto/rand}.
\item Pass the entropy buffer to
\texttt{circl.slhdsa.Scheme(params.ID).DeriveKey}.
\item Output $(sk_i, pk_i)$ in the FIPS 205 wire form: $sk_i =
(\mathit{SK.seed}, \mathit{SK.prf}, \mathit{PK.seed},
\mathit{PK.root})$ of length $4n$; $pk_i = (\mathit{PK.seed},
\mathit{PK.root})$ of length $2n$.
\end{enumerate}
\paragraph{Determinism.} If \texttt{rng} is a deterministic reader
(e.g., a \texttt{detReader} seeded by the canonical test fixture),
the output keypair is byte-identical across calls. This is the
property KAT regeneration relies on.
\paragraph{Independence.} No shared state. No correlation between
$(sk_i, pk_i)$ and $(sk_j, pk_j)$ for $i \ne j$. Each keypair is
generated on its own host with its own RNG.
\subsection{Per-validator signing primitive}
\label{sec:construction:sign}
\texttt{ValidatorSign} is the canonical signing primitive for the
public-BFT path. It is a thin wrapper over the FIPS 205 reference,
exposed with a public-BFT-explicit signature.
\paragraph{Algorithm (\texttt{standalone.go:231-244}, \texttt{sign.go}).}
\begin{enumerate}[leftmargin=2em,nosep]
\item Verify $sk_i$ is non-nil and the embedded mode matches an
installed \texttt{Params}.
\item Pull the \texttt{slhdsa.Scheme} for the embedded mode.
\item If \texttt{rng=nil}, invoke
\texttt{Scheme.SignDeterministic(sk\_i, msg, ctx=nil)}.
Otherwise invoke
\texttt{Scheme.SignRandomized(sk\_i, rng, msg, ctx=nil)}.
\item Return the raw FIPS 205 signature bytes
(\texttt{params.SignatureSize}: $16{,}224$, $35{,}664$, or
$29{,}792$ depending on mode).
\end{enumerate}
\paragraph{Context binding.} The \texttt{ctx} parameter is
intentionally omitted at the \texttt{ValidatorSign} surface: the
consensus layer binds chain-id / block-height / epoch into the
message $\mu$ upstream (the QuasarCert transcript-hash pattern).
The \texttt{Sign} entry point in \texttt{sign.go} retains the
\texttt{ctx} parameter for callers that need explicit context
binding; the \texttt{ValidatorSign} wrapper passes
\texttt{ctx=nil}.
\paragraph{Determinism.} With \texttt{rng=nil}, $\sigma_i$ is byte-
identical to what a single-party FIPS 205 signer would emit on
$(sk_i, \mu, \mathit{ctx}=\varepsilon)$. This is the load-bearing
property pinned by
$\textsc{TestMagnetar\_Wire\_FIPS205Verifiable}$ at
\texttt{wire\_test.go:166-220} (cf. \S\ref{sec:security:byte-equality}).
\subsection{Aggregate certificate construction}
\label{sec:construction:cert}
The consensus layer collects validator signatures into a
\texttt{ValidatorAggregateCert} for transport over the wire. The
cert is intentionally a parallel-slices shape (not a nested per-
signer record list) because it maps directly to the Z-Chain Groth16
rollup witness layout.
\paragraph{Wire layout
(\texttt{standalone.go:415-432}, \texttt{wire.go}).}
\begin{align*}
\mathit{ValidatorAggregateCert} \;=\; &
\mathit{Mode}_1 \,\Vert\, N_{\mathit{BE},4} \,\Vert\,
\mathit{Signers}[0]_{32} \,\Vert\, \cdots \,\Vert\,
\mathit{Signers}[N-1]_{32} \\
& \,\Vert\, \mathit{PubKeys}[0]_{|pk|} \,\Vert\, \cdots \,\Vert\,
\mathit{PubKeys}[N-1]_{|pk|} \\
& \,\Vert\, \mathit{Sigs}[0]_{|\sigma|} \,\Vert\, \cdots \,\Vert\,
\mathit{Sigs}[N-1]_{|\sigma|}.
\end{align*}
\paragraph{Algorithm \texttt{BuildAggregateCert}
(\texttt{standalone.go:443-478}).}
\begin{enumerate}[leftmargin=2em,nosep]
\item Validate \texttt{params} matches one of the canonical
parameter tables.
\item Validate the three parallel slices
$(\mathit{signers}, \mathit{pubKeys}, \mathit{sigs})$ have
the same length $N \ge 1$.
\item For each $i \in [0, N)$, validate $|pubKeys[i]| =
params.PublicKeySize$ and $|sigs[i]| = params.SignatureSize$.
\item Defensively copy each $pubKeys[i]$ and $sigs[i]$ (the
caller's slices may be aliased; the cert is wire-stable and
must not move under the caller).
\item Return the \texttt{ValidatorAggregateCert} record.
\end{enumerate}
\paragraph{Independent verifiability.} Each $\sigma_i$ in the cert
verifies independently against $pk_i$; there is no cross-signer
algebraic coupling. A relying party can verify any subset of the
cert (e.g., for incremental quorum-detection) without sharing any
secret state with other relying parties.
\subsection{Aggregate certificate verification}
\label{sec:construction:verifycert}
\texttt{VerifyAggregateCert} is the consumer-facing verification
primitive. It enforces registry binding (dropping unknown signers
and pubkey mismatches), dedupes repeated signers, runs the parallel
batch verify, and returns the count of valid signers. The quorum
decision lives at the consumer.
\paragraph{Algorithm (\texttt{standalone.go:519-613}).}
\begin{enumerate}[leftmargin=2em,nosep]
\item Validate \texttt{cert} is non-nil and the embedded
\texttt{Mode} matches a canonical parameter set.
\item Validate $N \ge 1$ and the parallel slices have matching
lengths.
\item For each $i \in [0, N)$, validate $|cert.PubKeys[i]| =
params.PublicKeySize$ and $|cert.Sigs[i]| =
params.SignatureSize$.
\item \emph{Phase 1 (entry classification).} For each $i$, dedupe
against \texttt{seen}, look up
$registered = knownValidators[cert.Signers[i]]$; mark the
entry verifiable only if (a) the lookup succeeds, (b)
$|registered| = params.PublicKeySize$, and (c)
$\textsc{ctEqualBytes}(cert.PubKeys[i], registered)$. Both
cases (a)/(b)/(c) failing are counted as INVALID, not
fatal.
\item \emph{Phase 2 (parallel batch verify).} Collect the
verifiable entries into flat slices and invoke
\texttt{ValidatorBatchVerify}, which dispatches serial or
goroutine-parallel based on the
\texttt{verifyAggregatedConcurrentThreshold} constant
(currently $\sim 16$).
\item \emph{Phase 3 (tally).} Count verifiable + valid entries.
Return the count.
\end{enumerate}
\paragraph{Defense-in-depth choices.}
\begin{enumerate}[leftmargin=2em,nosep]
\item \textbf{Unknown signers counted as INVALID, not fatal.} A
cert carrying a signature from a stale or unknown validator
does not crash the verifier; the verifier reports the
count, and the quorum policy at the consumer decides
whether to accept. This is the right discipline when certs
may be Z-Chain Groth16-rolled-up over a stale validator
registry: the verifier must not abort the entire batch on
one stale entry.
\item \textbf{Pubkey mismatches counted as INVALID, not fatal.}
Same rationale. An attacker who slips a SignedBundle from
an unknown validator into the cert does not gain a count
but does not crash the verifier.
\item \textbf{Duplicate signers deduped first-wins.} The first
occurrence wins; subsequent occurrences are silently
dropped (not counted twice, not counted as invalid). The
consensus layer can collect proof of double-signing
separately via per-signer \texttt{Sigs} comparison.
\item \textbf{Constant-time pubkey comparison.}
\texttt{ctEqualBytes} short-circuits at the wire-length
validation and otherwise runs a constant-time
byte-by-byte XOR-then-OR reduction. No secret-dependent
timing.
\end{enumerate}
\subsection{Canonical wire codec (MAGS / MAGG)}
\label{sec:construction:wire}
The wire codec at \texttt{wire.go} provides byte-stable framing for
individual signatures (MAGS) and group keys (MAGG), with a stateless
verifier dispatcher (\texttt{VerifyBytes}) for consensus-layer
consumption.
\paragraph{MAGS signature envelope.}
\begin{align*}
\mathit{MAGS\_frame} \;=\; & \mathtt{0x4D4147} \,\Vert\,
\mathtt{0x53} \,\Vert\, \mathit{version}_{\mathit{BE},2} \,\Vert\,
\mathit{mode}_1 \,\Vert\, \mathit{payloadLen}_{\mathit{BE},4} \\
& \,\Vert\, \mathit{sigPayload}_{params.SignatureSize}.
\end{align*}
Total length: $11 + params.SignatureSize$ bytes. The 11-byte fixed
header carries the magic ASCII string ``MAGS'', the version
(currently 1), the FIPS 205 mode byte, and the big-endian payload
length. The payload is the raw FIPS 205 signature bytes verbatim.
\paragraph{MAGG group-key envelope.}
\begin{align*}
\mathit{MAGG\_frame} \;=\; & \mathtt{0x4D4147} \,\Vert\,
\mathtt{0x47} \,\Vert\, \mathit{version}_{\mathit{BE},2} \,\Vert\,
\mathit{mode}_1 \,\Vert\, \mathit{payloadLen}_{\mathit{BE},4} \\
& \,\Vert\, \mathit{pkPayload}_{params.PublicKeySize}.
\end{align*}
Total length: $11 + params.PublicKeySize$ bytes. Same structure as
MAGS with a distinct magic suffix byte (``G'' vs ``S'').
\paragraph{Stateless \texttt{VerifyBytes} dispatcher.}
\begin{enumerate}[leftmargin=2em,nosep]
\item Validate $len(groupKeyWire) \ge 11$ and the magic matches
\texttt{MAGG}.
\item Validate $len(sigWire) \ge 11$ and the magic matches
\texttt{MAGS}.
\item Decode the version, mode, and declared payload length from
each frame. Validate
$len(groupKeyWire) = 11 + declared_{gk}$ and
$len(sigWire) = 11 + declared_{sig}$.
\item Validate both frames target the same mode.
\item Strip the 11-byte header from each, recovering the raw
FIPS 205 $pk_{raw}$ and $\sigma_{raw}$ payloads.
\item Dispatch to
\texttt{circl.slhdsa.Scheme(mode).PublicKey.UnmarshalBinary(
$pk_{raw}$)} and then
\texttt{Scheme.Verify($pk$, msg, $\sigma_{raw}$, ctx=nil)}.
\item Return the Boolean from the FIPS 205 verifier.
\end{enumerate}
\paragraph{Byte-identity to FIPS 205.} The wire codec is purely
additive (an envelope prefix); the payload bytes are the raw FIPS
205 signature or public-key bytes verbatim. Stripping the 11-byte
header recovers exactly what \texttt{cloudflare/circl/sign/slhdsa}
emits on the same $(sk, \mu, \mathit{ctx})$ tuple. The
$\textsc{TestMagnetar\_Wire\_FIPS205Verifiable}$ regression pins
this property across all three SHAKE parameter sets.
\subsection{Verify (FIPS 205 passthrough)}
\label{sec:construction:verify}
The per-signature \texttt{Verify} entry point at \texttt{verify.go}
is a thin dispatch to
\texttt{circl.slhdsa.PublicKey.UnmarshalBinary($pk.Bytes$)} followed
by
\texttt{slhdsa.Verify($pk$, NewMessage($\mu$), $\sigma$, ctx)}. No
Magnetar-specific envelope or wrapper. This is the FIPS 205-byte-
identity property: the produced signatures live in the exact FIPS
205 signature space, and any FIPS 205 verifier accepts them
unmodified after the wire-codec envelope (if present) is stripped.
\subsection{Constant-time discipline}
\label{sec:construction:ct}
The per-validator standalone primitive's hot paths are:
\begin{itemize}[leftmargin=2em,nosep]
\item \texttt{ValidatorSign} $\to$ \texttt{Sign} $\to$
\texttt{circl.slhdsa.SignDeterministic} (constant-time per
circl design).
\item \texttt{ValidatorBatchVerify} $\to$
\texttt{circl.slhdsa.Verify} (constant-time per circl
design; verifier accepts only public inputs).
\item \texttt{VerifyAggregateCert} pubkey-equality comparison
via \texttt{ctEqualBytes} (constant-time XOR-then-OR
reduction).
\item Wire-codec header validation via fixed-length big-endian
decode and constant-time magic comparison.
\end{itemize}
None of the standalone-path operations branch on secret material:
$sk_i$ is consumed only by \texttt{circl.slhdsa.SignDeterministic}
which is constant-time by construction. There are no Shamir
operations, no Lagrange interpolation, and no aggregator-held
secrets in the standalone path. The THBS-SE construction
(\S\ref{sec:thbsse}) carries additional constant-time obligations
on the byte-wise Lagrange reconstruct and the commit re-derivation
documented in \S\ref{sec:security:thbsse-tcb}.
\subsection{Identifiable abort (consensus layer)}
\label{sec:construction:abort}
The per-validator standalone primitive has a clean abort posture:
each $\sigma_i$ is a single-party FIPS 205 signature, and
\texttt{VerifyAggregateCert} reports the per-signer outcome via
the bitmap from \texttt{ValidatorBatchVerify}. The consensus layer
collects evidence of misbehaviour:
\begin{itemize}[leftmargin=2em,nosep]
\item \textbf{Invalid signature.} The verifier bitmap reports
$\mathit{false}$ for the offending signer. The
$(\mathsf{NodeID}_i, pk_i, \sigma_i, \mu)$ quad is
sufficient on-chain evidence for slashing.
\item \textbf{Double-signing.} Two valid signatures from the
same $\mathsf{NodeID}_i$ on distinct messages on the same
slot constitute evidence; the consensus layer collects
$(\mathsf{NodeID}_i, pk_i, \sigma_{i,A}, \sigma_{i,B},
\mu_A, \mu_B)$ as the slashing record.
\item \textbf{Unregistered signer.} An entry whose
$\mathsf{NodeID}_i$ does not appear in
\texttt{knownValidators} is counted as INVALID; the
consensus layer may treat repeated unregistered-signer
entries as a peer misbehaviour signal at the gossip layer.
\item \textbf{Pubkey-registry mismatch.} An entry whose embedded
$pk_i$ does not byte-equal the registered pubkey for that
$\mathsf{NodeID}_i$ is counted as INVALID; same handling
as unregistered signer.
\end{itemize}
The identifiable-abort taxonomy of THBS-SE (slot-guard
equivocation, Round-2 commit-bind failure, wire-size violation,
slot-mismatch reveal) is documented in
\S\ref{sec:thbsse:slashing}; both equivocation and malformed-share
evidence are pure-function third-party-verifiable.
\subsection{Wire sizes}
\label{sec:construction:wire-sizes}
\begin{table}[H]
\centering
\caption{Magnetar v1.0 per-validator standalone wire sizes.}
\label{tab:wire-sizes}
\begin{tabular}{lr}
\toprule
Object & Size (bytes) \\
\midrule
$\sigma_i$ FIPS 205 payload (SHAKE-192s) & 16\,224 \\
$\sigma_i$ FIPS 205 payload (SHAKE-192f) & 35\,664 \\
$\sigma_i$ FIPS 205 payload (SHAKE-256s) & 29\,792 \\
$pk_i$ FIPS 205 payload (SHAKE-192s / 192f) & 48 \\
$pk_i$ FIPS 205 payload (SHAKE-256s) & 64 \\
MAGS envelope header (fixed) & 11 \\
MAGG envelope header (fixed) & 11 \\
$\mathsf{NodeID}_i$ in cert & 32 \\
\midrule
\texttt{ValidatorAggregateCert} (SHAKE-192s, $K=21$) & $\sim 332$ KiB \\
\texttt{ValidatorAggregateCert} (SHAKE-192s, $K=21$) post Z-Chain Groth16 rollup & $\sim 192$ B \\
\bottomrule
\end{tabular}
\end{table}
The signature wire format is \emph{exactly} the FIPS 205 wire format
plus the 11-byte MAGS envelope; there is no Magnetar-specific
suffix, no per-signer ZK proof, and no out-of-band binding. The
Z-Chain Groth16 rollup that compresses $N \times |\sigma|$ to a
constant-size certificate is specified separately at the lux-zchain
layer and consumes the parallel-slices cert shape verbatim.